feat(docs): enhance interactive demos and improve documentation

- Add new interactive components for frontend routing, browser rendering pipeline, and database transactions
- Improve existing demos with better visuals, explanations, and examples
- Update documentation structure and content for better clarity
- Add new utility scripts and update package.json with new commands
- Fix formatting and alignment in documentation tables
This commit is contained in:
sanbuphy
2026-02-13 22:10:03 +08:00
parent 599052b2e0
commit d174ceea32
88 changed files with 26273 additions and 15539 deletions
@@ -2,98 +2,93 @@
<div class="access-key-management-demo">
<div class="demo-header">
<h4>访问密钥AK/SK生命周期管理</h4>
<p class="demo-desc">模拟 AK/SK 的创建使用和轮换流程</p>
<p class="intro-text">模拟 AK/SK 的创建使用和轮换流程</p>
</div>
<div class="lifecycle-container">
<!-- AK/SK Card -->
<div class="aksk-card">
<div class="card-header">
<span class="status-badge" :class="akStatus">{{ statusText }}</span>
<span class="age-indicator">已创建 {{ akAge }} </span>
</div>
<div class="demo-content">
<div class="lifecycle-container">
<!-- AK/SK Card -->
<div class="aksk-card">
<div class="card-header">
<span class="status-badge" :class="akStatus">{{ statusText }}</span>
<span class="age-indicator">已创建 {{ akAge }} </span>
</div>
<div class="credentials-display">
<div class="credential-row">
<span class="label">Access Key ID:</span>
<div class="value-container">
<span class="value">{{ maskedAK }}</span>
<button class="icon-btn" @click="toggleAKVisibility">
{{ showAK ? '🙈' : '👁' }}
</button>
<div class="credentials-display">
<div class="credential-row">
<span class="label">Access Key ID:</span>
<div class="value-container">
<span class="value">{{ maskedAK }}</span>
<button class="icon-btn" @click="toggleAKVisibility">
{{ showAK ? '🙈' : '👁' }}
</button>
</div>
</div>
<div class="credential-row">
<span class="label">Secret Access Key:</span>
<div class="value-container">
<span class="value">{{ maskedSK }}</span>
<button class="icon-btn" @click="toggleSKVisibility">
{{ showSK ? '🙈' : '👁' }}
</button>
</div>
</div>
</div>
<div class="credential-row">
<span class="label">Secret Access Key:</span>
<div class="value-container">
<span class="value">{{ maskedSK }}</span>
<button class="icon-btn" @click="toggleSKVisibility">
{{ showSK ? '🙈' : '👁' }}
</button>
<div class="usage-stats">
<div class="stat-item">
<span class="stat-value">{{ apiCalls }}</span>
<span class="stat-label">API 调用</span>
</div>
<div class="stat-item">
<span class="stat-value">{{ lastUsed }}</span>
<span class="stat-label">最后使用</span>
</div>
</div>
</div>
<div class="usage-stats">
<div class="stat-item">
<span class="stat-value">{{ apiCalls }}</span>
<span class="stat-label">API 调用</span>
</div>
<div class="stat-item">
<span class="stat-value">{{ lastUsed }}</span>
<span class="stat-label">最后使用</span>
</div>
<!-- Action Buttons -->
<div class="action-panel">
<button
class="action-btn primary"
@click="rotateKey"
:disabled="isRotating"
>
<span class="btn-icon">🔄</span>
<span class="btn-text">轮换密钥</span>
</button>
<button
class="action-btn warning"
@click="deactivateKey"
:disabled="akStatus === 'inactive'"
>
<span class="btn-icon"></span>
<span class="btn-text">{{ akStatus === 'inactive' ? '已禁用' : '禁用密钥' }}</span>
</button>
<button
class="action-btn danger"
@click="deleteKey"
>
<span class="btn-icon">🗑</span>
<span class="btn-text">删除密钥</span>
</button>
</div>
</div>
<!-- Action Buttons -->
<div class="action-panel">
<button
class="action-btn primary"
@click="rotateKey"
:disabled="isRotating"
>
<span class="btn-icon">🔄</span>
<span class="btn-text">轮换密钥</span>
</button>
<button
class="action-btn warning"
@click="deactivateKey"
:disabled="akStatus === 'inactive'"
>
<span class="btn-icon"></span>
<span class="btn-text">{{ akStatus === 'inactive' ? '已禁用' : '禁用密钥' }}</span>
</button>
<button
class="action-btn danger"
@click="deleteKey"
>
<span class="btn-icon">🗑</span>
<span class="btn-text">删除密钥</span>
</button>
<!-- Rotation Progress -->
<div class="rotation-progress" v-if="isRotating">
<div class="progress-bar">
<div class="progress-fill" :style="{ width: rotationProgress + '%' }"></div>
</div>
<span class="progress-text">{{ rotationStatus }}</span>
</div>
</div>
<!-- Rotation Progress -->
<div class="rotation-progress" v-if="isRotating">
<div class="progress-bar">
<div class="progress-fill" :style="{ width: rotationProgress + '%' }"></div>
</div>
<span class="progress-text">{{ rotationStatus }}</span>
</div>
<!-- Best Practices -->
<div class="best-practices">
<h5>🔒 AK/SK 安全管理最佳实践</h5>
<ul>
<li v-for="(tip, i) in securityTips" :key="i">
<span class="tip-icon">{{ tip.icon }}</span>
<span class="tip-text">{{ tip.text }}</span>
</li>
</ul>
<div class="info-box">
<strong>💡 安全提示</strong>访问密钥泄露是云安全事件的主要原因之一建议优先使用 IAM 角色替代访问密钥如果必须使用请务必定期轮换
</div>
</div>
</template>
@@ -198,129 +193,127 @@ function deleteKey() {
alert('密钥已删除(演示模式)')
}
}
// Security Tips
const securityTips = [
{ icon: '🔄', text: '每 90 天轮换一次访问密钥' },
{ icon: '🔒', text: '绝不将 AK/SK 硬编码在代码中' },
{ icon: '👁️', text: '定期审计和监控密钥使用情况' },
{ icon: '🗑️', text: '及时删除不再使用的访问密钥' },
{ icon: '🛡️', text: '优先使用 IAM 角色替代访问密钥' }
]
</script>
<style scoped>
.access-key-management-demo {
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
border-radius: 16px;
padding: 24px;
color: white;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg-soft);
border-radius: 8px;
padding: 1.5rem;
margin: 1rem 0;
max-height: 600px;
overflow-y: auto;
}
.demo-header {
text-align: center;
margin-bottom: 24px;
margin-bottom: 1rem;
}
.demo-header h4 {
margin: 0 0 8px 0;
font-size: 1.4rem;
margin: 0 0 0.5rem 0;
font-weight: 800;
color: var(--vp-c-text-1);
}
.demo-desc {
.intro-text {
margin: 0;
opacity: 0.9;
color: var(--vp-c-text-2);
font-size: 0.9rem;
}
.demo-content {
margin-bottom: 1rem;
}
.lifecycle-container {
display: grid;
grid-template-columns: 1fr auto;
gap: 20px;
margin-bottom: 20px;
gap: 1.25rem;
margin-bottom: 1.25rem;
}
/* AK/SK Card */
.aksk-card {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 12px;
padding: 20px;
border: 1px solid rgba(255, 255, 255, 0.2);
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
padding: 1.25rem;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
padding-bottom: 12px;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
margin-bottom: 1rem;
padding-bottom: 0.75rem;
border-bottom: 1px solid var(--vp-c-divider);
}
.status-badge {
padding: 4px 12px;
border-radius: 12px;
padding: 0.25rem 0.75rem;
border-radius: 4px;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
}
.status-badge.active {
background: #4caf50;
color: white;
background: var(--vp-c-brand-soft);
color: var(--vp-c-brand-1);
}
.status-badge.inactive {
background: #f44336;
color: white;
background: rgba(var(--vp-c-brand-delta-rgb), 0.15);
color: var(--vp-c-brand-delta);
}
.age-indicator {
font-size: 0.75rem;
opacity: 0.7;
color: var(--vp-c-text-3);
}
.credentials-display {
display: flex;
flex-direction: column;
gap: 12px;
margin-bottom: 16px;
gap: 0.75rem;
margin-bottom: 1rem;
}
.credential-row {
display: flex;
flex-direction: column;
gap: 4px;
gap: 0.25rem;
}
.credential-row .label {
font-size: 0.7rem;
opacity: 0.7;
color: var(--vp-c-text-3);
text-transform: uppercase;
}
.value-container {
display: flex;
align-items: center;
gap: 8px;
gap: 0.5rem;
}
.value {
flex: 1;
padding: 8px 12px;
background: rgba(0, 0, 0, 0.3);
padding: 0.5rem 0.75rem;
background: var(--vp-c-bg-alt);
border: 1px solid var(--vp-c-divider);
border-radius: 6px;
font-family: monospace;
font-family: var(--vp-font-family-mono);
font-size: 0.8rem;
word-break: break-all;
color: var(--vp-c-text-1);
}
.icon-btn {
padding: 8px;
background: rgba(255, 255, 255, 0.1);
border: none;
padding: 0.5rem;
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
border-radius: 6px;
cursor: pointer;
font-size: 1rem;
@@ -328,51 +321,53 @@ const securityTips = [
}
.icon-btn:hover {
background: rgba(255, 255, 255, 0.2);
background: var(--vp-c-bg-alt);
}
.usage-stats {
display: flex;
gap: 16px;
padding-top: 12px;
border-top: 1px solid rgba(255, 255, 255, 0.1);
gap: 1rem;
padding-top: 0.75rem;
border-top: 1px solid var(--vp-c-divider);
}
.stat-item {
display: flex;
flex-direction: column;
gap: 2px;
gap: 0.125rem;
}
.stat-value {
font-size: 1.2rem;
font-weight: 700;
color: #4caf50;
color: var(--vp-c-brand-1);
}
.stat-label {
font-size: 0.7rem;
opacity: 0.7;
color: var(--vp-c-text-3);
}
/* Action Panel */
.action-panel {
display: flex;
flex-direction: column;
gap: 12px;
gap: 0.75rem;
}
.action-btn {
display: flex;
align-items: center;
gap: 10px;
padding: 14px 18px;
border: none;
border-radius: 10px;
gap: 0.625rem;
padding: 0.875rem 1.125rem;
border: 1px solid var(--vp-c-divider);
border-radius: 6px;
cursor: pointer;
transition: all 0.3s ease;
transition: all 0.2s ease;
font-size: 0.9rem;
font-weight: 500;
background: var(--vp-c-bg);
color: var(--vp-c-text-1);
}
.action-btn:disabled {
@@ -381,33 +376,36 @@ const securityTips = [
}
.action-btn.primary {
background: linear-gradient(135deg, #4caf50 0%, #45a049 100%);
color: white;
background: var(--vp-c-brand);
border-color: var(--vp-c-brand);
color: var(--vp-c-bg);
}
.action-btn.primary:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(76, 175, 80, 0.3);
box-shadow: 0 4px 12px rgba(var(--vp-c-brand-rgb), 0.3);
}
.action-btn.warning {
background: linear-gradient(135deg, #ff9800 0%, #f57c00 100%);
color: white;
background: rgba(var(--vp-c-brand-delta-rgb), 0.1);
border-color: var(--vp-c-brand-delta);
color: var(--vp-c-brand-delta);
}
.action-btn.warning:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(255, 152, 0, 0.3);
box-shadow: 0 4px 12px rgba(var(--vp-c-brand-delta-rgb), 0.2);
}
.action-btn.danger {
background: linear-gradient(135deg, #f44336 0%, #d32f2f 100%);
color: white;
background: rgba(var(--vp-c-brand-delta-rgb), 0.15);
border-color: var(--vp-c-brand-delta);
color: var(--vp-c-brand-delta);
}
.action-btn.danger:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(244, 67, 54, 0.3);
box-shadow: 0 4px 12px rgba(var(--vp-c-brand-delta-rgb), 0.2);
}
.btn-icon {
@@ -416,24 +414,24 @@ const securityTips = [
/* Rotation Progress */
.rotation-progress {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 12px;
padding: 20px;
margin-top: 20px;
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
padding: 1.25rem;
margin-top: 1.25rem;
}
.progress-bar {
height: 8px;
background: rgba(255, 255, 255, 0.2);
background: var(--vp-c-bg-alt);
border-radius: 4px;
overflow: hidden;
margin-bottom: 12px;
margin-bottom: 0.75rem;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #4caf50 0%, #8bc34a 100%);
background: var(--vp-c-brand);
border-radius: 4px;
transition: width 0.3s ease;
}
@@ -442,49 +440,22 @@ const securityTips = [
display: block;
text-align: center;
font-size: 0.9rem;
opacity: 0.9;
color: var(--vp-c-text-2);
}
/* Best Practices */
.best-practices {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 12px;
padding: 20px;
margin-top: 20px;
.info-box {
padding: 0.75rem;
background: var(--vp-c-bg-alt);
border: 1px solid var(--vp-c-divider);
border-left: 4px solid var(--vp-c-brand);
border-radius: 6px;
font-size: 0.9rem;
line-height: 1.6;
color: var(--vp-c-text-2);
}
.best-practices h5 {
margin: 0 0 16px 0;
font-size: 1rem;
font-weight: 600;
}
.best-practices ul {
list-style: none;
padding: 0;
margin: 0;
}
.best-practices li {
display: flex;
align-items: center;
gap: 12px;
padding: 8px 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.best-practices li:last-child {
border-bottom: none;
}
.tip-icon {
font-size: 1.2rem;
}
.tip-text {
font-size: 0.85rem;
opacity: 0.9;
.info-box strong {
color: var(--vp-c-text-1);
}
@media (max-width: 768px) {
@@ -2,52 +2,58 @@
<div class="best-practices-demo">
<div class="demo-header">
<h4>云账号权限管理最佳实践清单</h4>
<p class="demo-desc">点击查看详细的实施指南和代码示例</p>
<p class="intro-text">点击查看详细的实施指南和代码示例</p>
</div>
<div class="practices-grid">
<div
v-for="(practice, index) in bestPractices"
:key="index"
class="practice-card"
:class="{ expanded: expandedCard === index }"
@click="toggleCard(index)"
>
<div class="card-header">
<div class="icon-wrapper" :style="{ background: practice.color }">
<span class="icon">{{ practice.icon }}</span>
</div>
<div class="title-wrapper">
<h5>{{ practice.title }}</h5>
<span class="priority" :class="practice.priority">{{ practice.priorityText }}</span>
</div>
<div class="expand-icon">{{ expandedCard === index ? '' : '+' }}</div>
</div>
<div class="card-body" v-if="expandedCard === index">
<p class="description">{{ practice.description }}</p>
<div class="checklist">
<h6> 检查清单</h6>
<ul>
<li v-for="(item, i) in practice.checklist" :key="i">{{ item }}</li>
</ul>
<div class="demo-content">
<div class="practices-grid">
<div
v-for="(practice, index) in bestPractices"
:key="index"
class="practice-card"
:class="{ expanded: expandedCard === index }"
@click="toggleCard(index)"
>
<div class="card-header">
<div class="icon-wrapper" :style="{ background: practice.color }">
<span class="icon">{{ practice.icon }}</span>
</div>
<div class="title-wrapper">
<h5>{{ practice.title }}</h5>
<span class="priority" :class="practice.priority">{{ practice.priorityText }}</span>
</div>
<div class="expand-icon">{{ expandedCard === index ? '' : '+' }}</div>
</div>
<div class="code-example" v-if="practice.code">
<h6>代码示例</h6>
<pre><code>{{ practice.code }}</code></pre>
</div>
<div class="card-body" v-if="expandedCard === index">
<p class="description">{{ practice.description }}</p>
<div class="tools" v-if="practice.tools">
<h6>推荐工具</h6>
<div class="tool-tags">
<span v-for="(tool, i) in practice.tools" :key="i" class="tool-tag">{{ tool }}</span>
<div class="checklist">
<h6> 检查清单</h6>
<ul>
<li v-for="(item, i) in practice.checklist" :key="i">{{ item }}</li>
</ul>
</div>
<div class="code-example" v-if="practice.code">
<h6>代码示例</h6>
<pre><code>{{ practice.code }}</code></pre>
</div>
<div class="tools" v-if="practice.tools">
<h6>推荐工具</h6>
<div class="tool-tags">
<span v-for="(tool, i) in practice.tools" :key="i" class="tool-tag">{{ tool }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="info-box">
<strong>💡 实施建议</strong>按照优先级从 P0 开始逐步实施最佳实践每个改进都能显著提升账号安全性不要试图一次性完成所有改进
</div>
</div>
</template>
@@ -62,7 +68,7 @@ const bestPractices = [
title: '根账号保护',
priority: 'p0',
priorityText: 'P0 - 最高优先级',
color: '#f44336',
color: 'rgba(var(--vp-c-brand-delta-rgb), 0.15)',
description: '根账号是云服务的所有者,拥有所有权限。必须实施最高级别的保护措施。',
checklist: [
'启用 MFA(推荐硬件 MFA 设备)',
@@ -84,7 +90,7 @@ aws iam attach-user-policy --user-name AdminUser \
title: '用户权限最小化',
priority: 'p0',
priorityText: 'P0 - 最高优先级',
color: '#ff9800',
color: 'rgba(var(--vp-c-brand-rgb), 0.1)',
description: '遵循最小权限原则,只授予用户完成工作所需的最低权限。',
checklist: [
'避免使用 AdministratorAccess 等全权限策略',
@@ -122,7 +128,7 @@ aws iam attach-user-policy --user-name AdminUser \
title: '优先使用 IAM 角色',
priority: 'p1',
priorityText: 'P1 - 高优先级',
color: '#4caf50',
color: 'var(--vp-c-brand-soft)',
description: 'IAM 角色没有长期凭证,通过临时凭证访问,大大降低凭证泄露风险。',
checklist: [
'EC2 实例使用实例角色(Instance Profile)',
@@ -160,7 +166,7 @@ s3_cross = boto3.client(
title: '访问密钥安全管理',
priority: 'p1',
priorityText: 'P1 - 高优先级',
color: '#2196f3',
color: 'rgba(var(--vp-c-brand-rgb), 0.1)',
description: '如果必须使用访问密钥(AK/SK),需要实施严格的安全管理措施。',
checklist: [
'绝不将 AK/SK 硬编码在代码或配置文件中',
@@ -208,7 +214,7 @@ s3 = boto3.client(
title: '监控与审计',
priority: 'p2',
priorityText: 'P2 - 中优先级',
color: '#9c27b0',
color: 'var(--vp-c-bg-alt)',
description: '建立全面的监控和审计机制,及时发现和响应安全事件。',
checklist: [
'启用 CloudTrail 记录所有 API 调用',
@@ -252,64 +258,69 @@ function toggleCard(index) {
<style scoped>
.best-practices-demo {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 16px;
padding: 24px;
color: white;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg-soft);
border-radius: 8px;
padding: 1.5rem;
margin: 1rem 0;
max-height: 600px;
overflow-y: auto;
}
.demo-header {
text-align: center;
margin-bottom: 24px;
margin-bottom: 1rem;
}
.demo-header h4 {
margin: 0 0 8px 0;
font-size: 1.4rem;
margin: 0 0 0.5rem 0;
font-weight: 800;
color: var(--vp-c-text-1);
}
.demo-desc {
.intro-text {
margin: 0;
opacity: 0.9;
color: var(--vp-c-text-2);
font-size: 0.9rem;
}
.demo-content {
margin-bottom: 1rem;
}
.practices-grid {
display: grid;
gap: 16px;
gap: 1rem;
}
.practice-card {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 12px;
padding: 20px;
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
padding: 1.25rem;
cursor: pointer;
transition: all 0.3s ease;
border: 1px solid rgba(255, 255, 255, 0.1);
transition: all 0.2s ease;
}
.practice-card:hover {
background: rgba(255, 255, 255, 0.15);
border-color: var(--vp-c-brand);
transform: translateY(-2px);
}
.practice-card.expanded {
background: rgba(255, 255, 255, 0.95);
color: #333;
border-color: var(--vp-c-brand);
background: var(--vp-c-bg-alt);
}
.card-header {
display: flex;
align-items: center;
gap: 16px;
gap: 1rem;
}
.icon-wrapper {
width: 48px;
height: 48px;
border-radius: 12px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
@@ -322,12 +333,14 @@ function toggleCard(index) {
}
.title-wrapper h5 {
margin: 0 0 4px 0;
margin: 0 0 0.25rem 0;
font-size: 1.1rem;
font-weight: 700;
color: var(--vp-c-text-1);
}
.priority {
padding: 2px 8px;
padding: 0.125rem 0.5rem;
border-radius: 4px;
font-size: 0.7rem;
font-weight: 600;
@@ -335,46 +348,48 @@ function toggleCard(index) {
}
.priority.p0 {
background: #f44336;
color: white;
background: rgba(var(--vp-c-brand-delta-rgb), 0.15);
color: var(--vp-c-brand-delta);
}
.priority.p1 {
background: #ff9800;
color: white;
background: rgba(var(--vp-c-brand-rgb), 0.1);
color: var(--vp-c-brand);
}
.priority.p2 {
background: #2196f3;
color: white;
background: var(--vp-c-brand-soft);
color: var(--vp-c-brand-1);
}
.expand-icon {
font-size: 1.5rem;
font-weight: 300;
opacity: 0.7;
color: var(--vp-c-text-3);
}
.card-body {
margin-top: 20px;
padding-top: 20px;
border-top: 1px solid #eee;
margin-top: 1.25rem;
padding-top: 1.25rem;
border-top: 1px solid var(--vp-c-divider);
}
.description {
font-size: 0.95rem;
line-height: 1.6;
margin-bottom: 16px;
margin-bottom: 1rem;
color: var(--vp-c-text-2);
}
.checklist {
margin-bottom: 20px;
margin-bottom: 1.25rem;
}
.checklist h6 {
margin: 0 0 12px 0;
margin: 0 0 0.75rem 0;
font-size: 0.9rem;
color: #667eea;
font-weight: 700;
color: var(--vp-c-brand-1);
}
.checklist ul {
@@ -384,65 +399,84 @@ function toggleCard(index) {
}
.checklist li {
padding: 6px 0;
padding-left: 24px;
padding: 0.375rem 0;
padding-left: 1.5rem;
position: relative;
font-size: 0.9rem;
color: var(--vp-c-text-2);
}
.checklist li:before {
content: '☐';
position: absolute;
left: 0;
color: #667eea;
color: var(--vp-c-brand);
}
.code-example {
margin-bottom: 20px;
margin-bottom: 1.25rem;
}
.code-example h6 {
margin: 0 0 12px 0;
margin: 0 0 0.75rem 0;
font-size: 0.9rem;
color: #667eea;
font-weight: 700;
color: var(--vp-c-brand-1);
}
.code-example pre {
background: #1e1e1e;
border-radius: 8px;
padding: 16px;
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
border-radius: 6px;
padding: 0.75rem;
overflow-x: auto;
margin: 0;
}
.code-example code {
color: #d4d4d4;
font-family: 'Consolas', 'Monaco', monospace;
color: var(--vp-c-text-2);
font-family: var(--vp-font-family-mono);
font-size: 0.8rem;
line-height: 1.5;
}
.tools h6 {
margin: 0 0 12px 0;
margin: 0 0 0.75rem 0;
font-size: 0.9rem;
color: #667eea;
font-weight: 700;
color: var(--vp-c-brand-1);
}
.tool-tags {
display: flex;
flex-wrap: wrap;
gap: 8px;
gap: 0.5rem;
}
.tool-tag {
padding: 4px 12px;
background: #e3f2fd;
color: #1565c0;
padding: 0.25rem 0.75rem;
background: var(--vp-c-brand-soft);
color: var(--vp-c-brand-1);
border-radius: 4px;
font-size: 0.8rem;
font-weight: 500;
}
.info-box {
padding: 0.75rem;
background: var(--vp-c-bg-alt);
border: 1px solid var(--vp-c-divider);
border-left: 4px solid var(--vp-c-brand);
border-radius: 6px;
font-size: 0.9rem;
line-height: 1.6;
color: var(--vp-c-text-2);
}
.info-box strong {
color: var(--vp-c-text-1);
}
@media (max-width: 768px) {
.card-header {
flex-wrap: wrap;
@@ -2,43 +2,44 @@
<div class="cross-account-access-demo">
<div class="demo-header">
<h4>跨账号访问流程演示</h4>
<p class="demo-desc">角色扮演AssumeRole获取临时凭证</p>
<p class="intro-text">角色扮演AssumeRole获取临时凭证</p>
</div>
<div class="flow-diagram">
<div class="account-box source">
<div class="account-header">账号 A源账号</div>
<div class="account-content">
<div class="entity">IAM User / Application</div>
<div class="action">调用 sts:AssumeRole</div>
<div class="demo-content">
<div class="flow-diagram">
<div class="account-box source">
<div class="account-header">账号 A源账号</div>
<div class="account-content">
<div class="entity">IAM User / Application</div>
<div class="action">调用 sts:AssumeRole</div>
</div>
</div>
<div class="arrow"></div>
<div class="account-box sts">
<div class="account-header">STS 服务</div>
<div class="account-content">
<div class="step">1. 验证源身份</div>
<div class="step">2. 检查信任策略</div>
<div class="step">3. 生成临时凭证</div>
</div>
</div>
<div class="arrow"></div>
<div class="account-box target">
<div class="account-header">账号 B目标账号</div>
<div class="account-content">
<div class="entity">CrossAccountRole</div>
<div class="resource">访问 S3 / EC2 等资源</div>
</div>
</div>
</div>
<div class="arrow"></div>
<div class="account-box sts">
<div class="account-header">STS 服务</div>
<div class="account-content">
<div class="step">1. 验证源身份</div>
<div class="step">2. 检查信任策略</div>
<div class="step">3. 生成临时凭证</div>
</div>
</div>
<div class="arrow"></div>
<div class="account-box target">
<div class="account-header">账号 B目标账号</div>
<div class="account-content">
<div class="entity">CrossAccountRole</div>
<div class="resource">访问 S3 / EC2 等资源</div>
</div>
</div>
</div>
<div class="code-example">
<h5>Python 代码示例</h5>
<pre><code>import boto3
<div class="code-example">
<h5>Python 代码示例</h5>
<pre><code>import boto3
# 在账号 A 中使用 IAM 用户凭证
sts_client = boto3.client('sts')
@@ -60,58 +61,74 @@ s3_client = boto3.client(
aws_secret_access_key=credentials['SecretAccessKey'],
aws_session_token=credentials['SessionToken']
)</code></pre>
</div>
</div>
<div class="info-box">
<strong>💡 跨账号访问优势</strong>通过角色扮演实现跨账号访问无需在每个账号创建 IAM 用户临时凭证自动过期更安全更易管理
</div>
</div>
</template>
<script setup>
// No script needed for this static demo
</script>
<style scoped>
.cross-account-access-demo {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 16px;
padding: 24px;
color: white;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg-soft);
border-radius: 8px;
padding: 1.5rem;
margin: 1rem 0;
max-height: 600px;
overflow-y: auto;
}
.demo-header {
text-align: center;
margin-bottom: 24px;
margin-bottom: 1rem;
}
.demo-header h4 {
margin: 0 0 8px 0;
font-size: 1.4rem;
margin: 0 0 0.5rem 0;
font-weight: 800;
color: var(--vp-c-text-1);
}
.demo-desc {
.intro-text {
margin: 0;
opacity: 0.9;
color: var(--vp-c-text-2);
font-size: 0.9rem;
}
.demo-content {
margin-bottom: 1rem;
}
.flow-diagram {
display: flex;
align-items: center;
justify-content: center;
gap: 16px;
margin-bottom: 24px;
gap: 1rem;
margin-bottom: 1.5rem;
flex-wrap: wrap;
}
.account-box {
background: rgba(255, 255, 255, 0.95);
border-radius: 12px;
padding: 16px;
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
padding: 1rem;
min-width: 180px;
color: #333;
}
.account-header {
font-weight: 700;
font-size: 0.85rem;
margin-bottom: 12px;
padding-bottom: 8px;
border-bottom: 2px solid #eee;
margin-bottom: 0.75rem;
padding-bottom: 0.5rem;
border-bottom: 1px solid var(--vp-c-divider);
color: var(--vp-c-text-1);
}
.account-content {
@@ -119,23 +136,23 @@ s3_client = boto3.client(
}
.entity {
background: #e3f2fd;
padding: 6px 10px;
background: var(--vp-c-brand-soft);
padding: 0.375rem 0.625rem;
border-radius: 4px;
margin-bottom: 8px;
color: #1565c0;
margin-bottom: 0.5rem;
color: var(--vp-c-brand-1);
font-weight: 500;
}
.action {
color: #666;
color: var(--vp-c-text-3);
font-style: italic;
}
.step {
padding: 4px 0;
color: #666;
border-bottom: 1px solid #f0f0f0;
padding: 0.25rem 0;
color: var(--vp-c-text-2);
border-bottom: 1px solid var(--vp-c-divider);
}
.step:last-child {
@@ -143,28 +160,30 @@ s3_client = boto3.client(
}
.resource {
background: #e8f5e9;
padding: 6px 10px;
background: rgba(var(--vp-c-brand-rgb), 0.1);
padding: 0.375rem 0.625rem;
border-radius: 4px;
margin-top: 8px;
color: #2e7d32;
margin-top: 0.5rem;
color: var(--vp-c-brand);
}
.arrow {
font-size: 2rem;
color: rgba(255, 255, 255, 0.8);
color: var(--vp-c-text-3);
}
.code-example {
background: #1e1e1e;
border-radius: 12px;
padding: 20px;
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
padding: 1.25rem;
}
.code-example h5 {
margin: 0 0 12px 0;
color: #fff;
margin: 0 0 0.75rem 0;
color: var(--vp-c-text-1);
font-size: 0.9rem;
font-weight: 700;
}
.code-example pre {
@@ -173,12 +192,27 @@ s3_client = boto3.client(
}
.code-example code {
color: #d4d4d4;
font-family: 'Consolas', 'Monaco', monospace;
color: var(--vp-c-text-2);
font-family: var(--vp-font-family-mono);
font-size: 0.8rem;
line-height: 1.5;
}
.info-box {
padding: 0.75rem;
background: var(--vp-c-bg-alt);
border: 1px solid var(--vp-c-divider);
border-left: 4px solid var(--vp-c-brand);
border-radius: 6px;
font-size: 0.9rem;
line-height: 1.6;
color: var(--vp-c-text-2);
}
.info-box strong {
color: var(--vp-c-text-1);
}
@media (max-width: 768px) {
.flow-diagram {
flex-direction: column;
@@ -2,79 +2,85 @@
<div class="iam-ram-comparison-demo">
<div class="demo-header">
<h4>AWS IAM vs 阿里云 RAM 对比</h4>
<p class="demo-desc">点击各个模块查看详细对比</p>
<p class="intro-text">点击各个模块查看详细对比</p>
</div>
<div class="comparison-container">
<!-- AWS IAM Column -->
<div class="platform-column aws-column">
<div class="platform-header aws">
<div class="logo">AWS</div>
<h5>IAM</h5>
<span class="subtitle">Identity and Access Management</span>
<div class="demo-content">
<div class="comparison-container">
<!-- AWS IAM Column -->
<div class="platform-column aws-column">
<div class="platform-header aws">
<div class="logo">AWS</div>
<h5>IAM</h5>
<span class="subtitle">Identity and Access Management</span>
</div>
<div class="features-list">
<div
v-for="(feature, index) in awsFeatures"
:key="index"
class="feature-item"
:class="{ active: selectedFeature === `aws-${index}` }"
@click="selectFeature('aws', index)"
>
<div class="feature-icon">{{ feature.icon }}</div>
<div class="feature-content">
<span class="feature-name">{{ feature.name }}</span>
<span class="feature-desc">{{ feature.desc }}</span>
</div>
</div>
</div>
</div>
<div class="features-list">
<div
v-for="(feature, index) in awsFeatures"
:key="index"
class="feature-item"
:class="{ active: selectedFeature === `aws-${index}` }"
@click="selectFeature('aws', index)"
>
<div class="feature-icon">{{ feature.icon }}</div>
<div class="feature-content">
<span class="feature-name">{{ feature.name }}</span>
<span class="feature-desc">{{ feature.desc }}</span>
<!-- Comparison Details -->
<div class="comparison-details" v-if="selectedFeatureData">
<div class="detail-card">
<h6>{{ selectedFeatureData.name }}</h6>
<div class="comparison-row">
<div class="aws-detail">
<span class="label">AWS IAM</span>
<p>{{ selectedFeatureData.awsDetail }}</p>
<code v-if="selectedFeatureData.awsExample">{{ selectedFeatureData.awsExample }}</code>
</div>
<div class="vs-divider">VS</div>
<div class="ram-detail">
<span class="label">阿里云 RAM</span>
<p>{{ selectedFeatureData.ramDetail }}</p>
<code v-if="selectedFeatureData.ramExample">{{ selectedFeatureData.ramExample }}</code>
</div>
</div>
</div>
</div>
<!-- Alibaba Cloud RAM Column -->
<div class="platform-column ram-column">
<div class="platform-header ram">
<div class="logo">阿里云</div>
<h5>RAM</h5>
<span class="subtitle">Resource Access Management</span>
</div>
<div class="features-list">
<div
v-for="(feature, index) in ramFeatures"
:key="index"
class="feature-item"
:class="{ active: selectedFeature === `ram-${index}` }"
@click="selectFeature('ram', index)"
>
<div class="feature-icon">{{ feature.icon }}</div>
<div class="feature-content">
<span class="feature-name">{{ feature.name }}</span>
<span class="feature-desc">{{ feature.desc }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Comparison Details -->
<div class="comparison-details" v-if="selectedFeatureData">
<div class="detail-card">
<h6>{{ selectedFeatureData.name }}</h6>
<div class="comparison-row">
<div class="aws-detail">
<span class="label">AWS IAM</span>
<p>{{ selectedFeatureData.awsDetail }}</p>
<code v-if="selectedFeatureData.awsExample">{{ selectedFeatureData.awsExample }}</code>
</div>
<div class="vs-divider">VS</div>
<div class="ram-detail">
<span class="label">阿里云 RAM</span>
<p>{{ selectedFeatureData.ramDetail }}</p>
<code v-if="selectedFeatureData.ramExample">{{ selectedFeatureData.ramExample }}</code>
</div>
</div>
</div>
</div>
<!-- Alibaba Cloud RAM Column -->
<div class="platform-column ram-column">
<div class="platform-header ram">
<div class="logo">阿里云</div>
<h5>RAM</h5>
<span class="subtitle">Resource Access Management</span>
</div>
<div class="features-list">
<div
v-for="(feature, index) in ramFeatures"
:key="index"
class="feature-item"
:class="{ active: selectedFeature === `ram-${index}` }"
@click="selectFeature('ram', index)"
>
<div class="feature-icon">{{ feature.icon }}</div>
<div class="feature-content">
<span class="feature-name">{{ feature.name }}</span>
<span class="feature-desc">{{ feature.desc }}</span>
</div>
</div>
</div>
</div>
<div class="info-box">
<strong>💡 提示</strong>IAM RAM 的核心概念基本一致只是术语和实现细节略有不同掌握一个平台后可以快速迁移到另一个平台
</div>
</div>
</template>
@@ -154,91 +160,101 @@ function selectFeature(platform, index) {
<style scoped>
.iam-ram-comparison-demo {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 16px;
padding: 24px;
color: white;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg-soft);
border-radius: 8px;
padding: 1.5rem;
margin: 1rem 0;
max-height: 600px;
overflow-y: auto;
}
.demo-header {
text-align: center;
margin-bottom: 24px;
margin-bottom: 1rem;
}
.demo-header h4 {
margin: 0 0 8px 0;
font-size: 1.5rem;
margin: 0 0 0.5rem 0;
font-weight: 800;
color: var(--vp-c-text-1);
}
.demo-desc {
.intro-text {
margin: 0;
opacity: 0.9;
color: var(--vp-c-text-2);
font-size: 0.9rem;
}
.demo-content {
margin-bottom: 1rem;
}
.comparison-container {
display: grid;
grid-template-columns: 1fr 1.5fr 1fr;
gap: 16px;
gap: 1rem;
align-items: start;
}
.platform-column {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 12px;
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
overflow: hidden;
}
.platform-header {
padding: 16px;
padding: 1rem;
text-align: center;
border-bottom: 1px solid var(--vp-c-divider);
}
.platform-header.aws {
background: linear-gradient(135deg, #ff9900 0%, #ff6600 100%);
background: var(--vp-c-brand-soft);
}
.platform-header.ram {
background: linear-gradient(135deg, #ff6a00 0%, #ee0979 100%);
background: rgba(var(--vp-c-brand-delta-rgb), 0.15);
}
.platform-header .logo {
font-size: 1.2rem;
font-weight: bold;
margin-bottom: 4px;
margin-bottom: 0.25rem;
}
.platform-header h5 {
margin: 0;
font-size: 1.1rem;
color: var(--vp-c-text-1);
}
.platform-header .subtitle {
font-size: 0.7rem;
opacity: 0.9;
color: var(--vp-c-text-2);
}
.features-list {
padding: 12px;
padding: 0.75rem;
}
.feature-item {
display: flex;
align-items: center;
gap: 10px;
padding: 10px;
margin-bottom: 8px;
background: rgba(255, 255, 255, 0.1);
border-radius: 8px;
gap: 0.5rem;
padding: 0.625rem;
margin-bottom: 0.5rem;
background: var(--vp-c-bg-alt);
border: 1px solid var(--vp-c-divider);
border-radius: 6px;
cursor: pointer;
transition: all 0.3s ease;
transition: all 0.2s ease;
}
.feature-item:hover,
.feature-item.active {
background: rgba(255, 255, 255, 0.2);
background: var(--vp-c-brand-soft);
border-color: var(--vp-c-brand);
transform: translateX(4px);
}
@@ -255,21 +271,22 @@ function selectFeature(platform, index) {
.feature-name {
font-weight: 600;
font-size: 0.85rem;
color: var(--vp-c-text-1);
}
.feature-desc {
font-size: 0.7rem;
opacity: 0.8;
color: var(--vp-c-text-3);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.comparison-details {
background: rgba(255, 255, 255, 0.95);
border-radius: 12px;
padding: 20px;
color: #333;
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
padding: 1rem;
}
.detail-card {
@@ -277,31 +294,25 @@ function selectFeature(platform, index) {
}
.detail-card h6 {
margin: 0 0 16px 0;
margin: 0 0 1rem 0;
font-size: 1.1rem;
color: #667eea;
color: var(--vp-c-brand-1);
}
.comparison-row {
display: flex;
align-items: stretch;
gap: 16px;
gap: 1rem;
}
.aws-detail,
.ram-detail {
flex: 1;
padding: 12px;
border-radius: 8px;
padding: 0.75rem;
border-radius: 6px;
text-align: left;
}
.aws-detail {
background: linear-gradient(135deg, #fff8e1 0%, #ffecb3 100%);
}
.ram-detail {
background: linear-gradient(135deg, #fce4ec 0%, #f8bbd9 100%);
background: var(--vp-c-bg-alt);
border: 1px solid var(--vp-c-divider);
}
.aws-detail .label,
@@ -309,32 +320,36 @@ function selectFeature(platform, index) {
display: block;
font-weight: 700;
font-size: 0.8rem;
margin-bottom: 6px;
margin-bottom: 0.375rem;
color: var(--vp-c-text-1);
}
.aws-detail .label {
color: #ff6f00;
color: var(--vp-c-brand-1);
}
.ram-detail .label {
color: #c2185b;
color: var(--vp-c-brand-delta);
}
.aws-detail p,
.ram-detail p {
margin: 0 0 8px 0;
margin: 0 0 0.5rem 0;
font-size: 0.8rem;
line-height: 1.4;
color: var(--vp-c-text-2);
}
.aws-detail code,
.ram-detail code {
display: block;
padding: 6px;
background: rgba(0, 0, 0, 0.05);
padding: 0.375rem;
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
border-radius: 4px;
font-size: 0.65rem;
word-break: break-all;
color: var(--vp-c-text-2);
}
.vs-divider {
@@ -343,14 +358,29 @@ function selectFeature(platform, index) {
justify-content: center;
font-weight: 700;
font-size: 0.9rem;
color: #999;
padding: 0 8px;
color: var(--vp-c-text-3);
padding: 0 0.5rem;
}
.info-box {
padding: 0.75rem;
background: var(--vp-c-bg-alt);
border: 1px solid var(--vp-c-divider);
border-left: 4px solid var(--vp-c-brand);
border-radius: 6px;
font-size: 0.9rem;
line-height: 1.6;
color: var(--vp-c-text-2);
}
.info-box strong {
color: var(--vp-c-text-1);
}
@media (max-width: 1024px) {
.comparison-container {
grid-template-columns: 1fr;
gap: 20px;
gap: 1rem;
}
.comparison-details {
@@ -362,7 +392,7 @@ function selectFeature(platform, index) {
}
.vs-divider {
padding: 8px 0;
padding: 0.5rem 0;
}
}
</style>
@@ -2,37 +2,43 @@
<div class="identity-provider-demo">
<div class="demo-header">
<h4>身份提供商(IdP)集成流程</h4>
<p class="demo-desc">点击步骤查看 SSO 单点登录流程</p>
<p class="intro-text">点击步骤查看 SSO 单点登录流程</p>
</div>
<div class="sso-flow">
<div class="flow-step" v-for="(step, index) in steps" :key="index"
:class="{ active: currentStep === index, completed: currentStep > index }"
@click="goToStep(index)">
<div class="step-number">{{ index + 1 }}</div>
<div class="step-content">
<span class="step-title">{{ step.title }}</span>
<span class="step-desc">{{ step.desc }}</span>
<div class="demo-content">
<div class="sso-flow">
<div class="flow-step" v-for="(step, index) in steps" :key="index"
:class="{ active: currentStep === index, completed: currentStep > index }"
@click="goToStep(index)">
<div class="step-number">{{ index + 1 }}</div>
<div class="step-content">
<span class="step-title">{{ step.title }}</span>
<span class="step-desc">{{ step.desc }}</span>
</div>
<div class="step-arrow" v-if="index < steps.length - 1"></div>
</div>
</div>
<div class="detail-panel" v-if="currentStepData">
<h5>{{ currentStepData.title }}</h5>
<p>{{ currentStepData.detail }}</p>
<div class="code-block" v-if="currentStepData.code">
<pre><code>{{ currentStepData.code }}</code></pre>
</div>
<div class="entity-flow" v-if="currentStepData.flow">
<div class="flow-row" v-for="(row, i) in currentStepData.flow" :key="i">
<span class="entity" :class="row.from.type">{{ row.from.name }}</span>
<span class="action">{{ row.action }}</span>
<span class="entity" :class="row.to.type">{{ row.to.name }}</span>
</div>
</div>
<div class="step-arrow" v-if="index < steps.length - 1"></div>
</div>
</div>
<div class="detail-panel" v-if="currentStepData">
<h5>{{ currentStepData.title }}</h5>
<p>{{ currentStepData.detail }}</p>
<div class="code-block" v-if="currentStepData.code">
<pre><code>{{ currentStepData.code }}</code></pre>
</div>
<div class="entity-flow" v-if="currentStepData.flow">
<div class="flow-row" v-for="(row, i) in currentStepData.flow" :key="i">
<span class="entity" :class="row.from.type">{{ row.from.name }}</span>
<span class="action">{{ row.action }}</span>
<span class="entity" :class="row.to.type">{{ row.to.name }}</span>
</div>
</div>
<div class="info-box">
<strong>💡 SSO 优势</strong>通过企业 IdP 统一管理用户身份避免在每个云平台单独创建账号提高安全性和管理效率
</div>
</div>
</template>
@@ -164,70 +170,86 @@ function goToStep(index) {
<style scoped>
.identity-provider-demo {
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
border-radius: 16px;
padding: 24px;
color: white;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg-soft);
border-radius: 8px;
padding: 1.5rem;
margin: 1rem 0;
max-height: 600px;
overflow-y: auto;
}
.demo-header {
text-align: center;
margin-bottom: 24px;
margin-bottom: 1rem;
}
.demo-header h4 {
margin: 0 0 8px 0;
font-size: 1.4rem;
margin: 0 0 0.5rem 0;
font-weight: 800;
color: var(--vp-c-text-1);
}
.demo-desc {
.intro-text {
margin: 0;
opacity: 0.9;
color: var(--vp-c-text-2);
font-size: 0.9rem;
}
.demo-content {
margin-bottom: 1rem;
}
.sso-flow {
display: flex;
flex-wrap: wrap;
gap: 8px;
gap: 0.5rem;
justify-content: center;
margin-bottom: 24px;
margin-bottom: 1.5rem;
}
.flow-step {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 8px;
gap: 0.5rem;
padding: 0.5rem 0.75rem;
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
border-radius: 6px;
cursor: pointer;
transition: all 0.3s ease;
transition: all 0.2s ease;
min-width: 120px;
}
.flow-step:hover,
.flow-step.active {
background: rgba(255, 255, 255, 0.25);
background: var(--vp-c-brand-soft);
border-color: var(--vp-c-brand);
transform: scale(1.02);
}
.flow-step.completed {
background: rgba(76, 175, 80, 0.3);
background: rgba(var(--vp-c-brand-rgb), 0.1);
border-color: var(--vp-c-brand);
}
.step-number {
width: 24px;
height: 24px;
background: rgba(255, 255, 255, 0.3);
background: var(--vp-c-bg-alt);
border: 1px solid var(--vp-c-divider);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.75rem;
font-weight: bold;
color: var(--vp-c-text-1);
}
.flow-step.active .step-number {
background: var(--vp-c-brand);
border-color: var(--vp-c-brand);
color: var(--vp-c-bg);
}
.step-content {
@@ -239,44 +261,48 @@ function goToStep(index) {
.step-title {
font-size: 0.75rem;
font-weight: 600;
color: var(--vp-c-text-1);
white-space: nowrap;
}
.step-desc {
font-size: 0.6rem;
opacity: 0.8;
color: var(--vp-c-text-3);
white-space: nowrap;
}
.step-arrow {
font-size: 1.2rem;
opacity: 0.6;
color: var(--vp-c-text-3);
}
.detail-panel {
background: rgba(255, 255, 255, 0.95);
border-radius: 12px;
padding: 20px;
color: #333;
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
padding: 1rem;
}
.detail-panel h5 {
margin: 0 0 12px 0;
margin: 0 0 0.75rem 0;
font-size: 1.1rem;
color: #1e3c72;
font-weight: 700;
color: var(--vp-c-brand-1);
}
.detail-panel p {
margin: 0 0 16px 0;
margin: 0 0 1rem 0;
font-size: 0.9rem;
line-height: 1.5;
color: var(--vp-c-text-2);
}
.code-block {
background: #1e1e1e;
border-radius: 8px;
padding: 12px;
margin-bottom: 16px;
background: var(--vp-c-bg-alt);
border: 1px solid var(--vp-c-divider);
border-radius: 6px;
padding: 0.75rem;
margin-bottom: 1rem;
overflow-x: auto;
}
@@ -285,61 +311,77 @@ function goToStep(index) {
}
.code-block code {
color: #d4d4d4;
color: var(--vp-c-text-2);
font-size: 0.75rem;
line-height: 1.4;
font-family: 'Consolas', 'Monaco', monospace;
font-family: var(--vp-font-family-mono);
}
.entity-flow {
display: flex;
flex-direction: column;
gap: 8px;
gap: 0.5rem;
}
.flow-row {
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
padding: 8px;
background: #f5f5f5;
gap: 0.75rem;
padding: 0.5rem;
background: var(--vp-c-bg-alt);
border-radius: 6px;
}
.entity {
padding: 4px 10px;
padding: 0.25rem 0.625rem;
border-radius: 4px;
font-size: 0.8rem;
font-weight: 500;
}
.entity.user {
background: #e3f2fd;
color: #1565c0;
background: var(--vp-c-brand-soft);
color: var(--vp-c-brand-1);
}
.entity.app {
background: #f3e5f5;
color: #7b1fa2;
background: rgba(var(--vp-c-brand-delta-rgb), 0.15);
color: var(--vp-c-brand-delta);
}
.entity.idp {
background: #e8f5e9;
color: #2e7d32;
background: rgba(var(--vp-c-brand-rgb), 0.1);
color: var(--vp-c-brand);
}
.entity.cloud {
background: #fff3e0;
color: #ef6c00;
background: var(--vp-c-bg-soft);
border: 1px solid var(--vp-c-divider);
color: var(--vp-c-text-1);
}
.action {
font-size: 0.75rem;
color: #666;
color: var(--vp-c-text-3);
font-weight: 500;
}
.info-box {
padding: 0.75rem;
background: var(--vp-c-bg-alt);
border: 1px solid var(--vp-c-divider);
border-left: 4px solid var(--vp-c-brand);
border-radius: 6px;
font-size: 0.9rem;
line-height: 1.6;
color: var(--vp-c-text-2);
}
.info-box strong {
color: var(--vp-c-text-1);
}
@media (max-width: 768px) {
.sso-flow {
flex-direction: column;
@@ -356,7 +398,7 @@ function goToStep(index) {
.flow-row {
flex-direction: column;
gap: 4px;
gap: 0.25rem;
}
}
</style>
@@ -2,59 +2,55 @@
<div class="mfa-security-demo">
<div class="demo-header">
<h4>MFA 多因素认证模拟</h4>
<p class="demo-desc">体验 MFA 双因素认证流程</p>
<p class="intro-text">体验 MFA 双因素认证流程</p>
</div>
<div class="mfa-flow">
<div class="auth-step" :class="{ active: step >= 1, completed: step > 1 }">
<div class="step-icon">🔐</div>
<div class="step-label">密码验证</div>
</div>
<div class="step-arrow"></div>
<div class="auth-step" :class="{ active: step >= 2, completed: step > 2 }">
<div class="step-icon">📱</div>
<div class="step-label">MFA 验证</div>
</div>
<div class="step-arrow"></div>
<div class="auth-step" :class="{ active: step >= 3 }">
<div class="step-icon"></div>
<div class="step-label">登录成功</div>
</div>
</div>
<div class="auth-panel" v-if="step === 1">
<h5>请输入密码</h5>
<input type="password" v-model="password" placeholder="输入密码" @keyup.enter="verifyPassword" />
<button @click="verifyPassword" :disabled="!password">验证密码</button>
</div>
<div class="auth-panel" v-if="step === 2">
<h5>MFA 验证</h5>
<div class="totp-display">
<span class="totp-code">{{ totpCode }}</span>
<div class="totp-timer">
<div class="timer-bar" :style="{ width: timerWidth + '%' }"></div>
<div class="demo-content">
<div class="mfa-flow">
<div class="auth-step" :class="{ active: step >= 1, completed: step > 1 }">
<div class="step-icon">🔐</div>
<div class="step-label">密码验证</div>
</div>
<div class="step-arrow"></div>
<div class="auth-step" :class="{ active: step >= 2, completed: step > 2 }">
<div class="step-icon">📱</div>
<div class="step-label">MFA 验证</div>
</div>
<div class="step-arrow"></div>
<div class="auth-step" :class="{ active: step >= 3 }">
<div class="step-icon"></div>
<div class="step-label">登录成功</div>
</div>
</div>
<input type="text" v-model="userCode" placeholder="输入6位验证码" maxlength="6" @keyup.enter="verifyMFA" />
<button @click="verifyMFA" :disabled="userCode.length !== 6">验证</button>
<div class="auth-panel" v-if="step === 1">
<h5>请输入密码</h5>
<input type="password" v-model="password" placeholder="输入密码" @keyup.enter="verifyPassword" />
<button @click="verifyPassword" :disabled="!password">验证密码</button>
</div>
<div class="auth-panel" v-if="step === 2">
<h5>MFA 验证</h5>
<div class="totp-display">
<span class="totp-code">{{ totpCode }}</span>
<div class="totp-timer">
<div class="timer-bar" :style="{ width: timerWidth + '%' }"></div>
</div>
</div>
<input type="text" v-model="userCode" placeholder="输入6位验证码" maxlength="6" @keyup.enter="verifyMFA" />
<button @click="verifyMFA" :disabled="userCode.length !== 6">验证</button>
</div>
<div class="success-message" v-if="step === 3">
<div class="success-icon">🎉</div>
<h5>登录成功</h5>
<p>已通过 MFA 双因素认证</p>
<button @click="reset">重新演示</button>
</div>
</div>
<div class="success-message" v-if="step === 3">
<div class="success-icon">🎉</div>
<h5>登录成功</h5>
<p>已通过 MFA 双因素认证</p>
<button @click="reset">重新演示</button>
</div>
<div class="security-tips">
<h5>💡 MFA 安全提示</h5>
<ul>
<li>启用 MFA 可降低 99.9% 的账号被盗风险</li>
<li>推荐使用 TOTP 应用Google AuthenticatorMicrosoft Authenticator</li>
<li>硬件安全密钥 YubiKey提供最高级别的安全性</li>
<li>务必备份 MFA 恢复码防止设备丢失无法登录</li>
</ul>
<div class="info-box">
<strong>💡 MFA 安全价值</strong>启用 MFA 可降低 99.9% 的账号被盗风险即使密码泄露攻击者没有你的 MFA 设备也无法登录
</div>
</div>
</template>
@@ -118,35 +114,41 @@ onUnmounted(() => {
<style scoped>
.mfa-security-demo {
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
border-radius: 16px;
padding: 24px;
color: white;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg-soft);
border-radius: 8px;
padding: 1.5rem;
margin: 1rem 0;
max-height: 600px;
overflow-y: auto;
}
.demo-header {
text-align: center;
margin-bottom: 24px;
margin-bottom: 1rem;
}
.demo-header h4 {
margin: 0 0 8px 0;
font-size: 1.4rem;
margin: 0 0 0.5rem 0;
font-weight: 800;
color: var(--vp-c-text-1);
}
.demo-desc {
.intro-text {
margin: 0;
opacity: 0.9;
color: var(--vp-c-text-2);
font-size: 0.9rem;
}
.demo-content {
margin-bottom: 1rem;
}
.mfa-flow {
display: flex;
justify-content: center;
align-items: center;
gap: 16px;
margin-bottom: 24px;
gap: 1rem;
margin-bottom: 1.5rem;
flex-wrap: wrap;
}
@@ -154,24 +156,25 @@ onUnmounted(() => {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
padding: 16px;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 12px;
gap: 0.5rem;
padding: 1rem;
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
border-radius: 6px;
opacity: 0.5;
transition: all 0.3s ease;
transition: all 0.2s ease;
}
.auth-step.active {
opacity: 1;
background: rgba(76, 175, 80, 0.2);
border: 1px solid rgba(76, 175, 80, 0.5);
background: var(--vp-c-brand-soft);
border-color: var(--vp-c-brand);
}
.auth-step.completed {
opacity: 1;
background: rgba(76, 175, 80, 0.3);
background: rgba(var(--vp-c-brand-rgb), 0.1);
border-color: var(--vp-c-brand);
}
.step-icon {
@@ -181,53 +184,56 @@ onUnmounted(() => {
.step-label {
font-size: 0.8rem;
font-weight: 500;
color: var(--vp-c-text-1);
}
.step-arrow {
font-size: 1.5rem;
opacity: 0.6;
color: var(--vp-c-text-3);
}
.auth-panel {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 12px;
padding: 24px;
margin-bottom: 20px;
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
padding: 1.5rem;
margin-bottom: 1.25rem;
}
.auth-panel h5 {
margin: 0 0 16px 0;
margin: 0 0 1rem 0;
font-size: 1.1rem;
font-weight: 700;
color: var(--vp-c-text-1);
}
.auth-panel input {
width: 100%;
padding: 12px 16px;
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 8px;
background: rgba(0, 0, 0, 0.2);
color: white;
padding: 0.75rem 1rem;
border: 1px solid var(--vp-c-divider);
border-radius: 6px;
background: var(--vp-c-bg-alt);
color: var(--vp-c-text-1);
font-size: 1rem;
margin-bottom: 12px;
margin-bottom: 0.75rem;
box-sizing: border-box;
}
.auth-panel input::placeholder {
color: rgba(255, 255, 255, 0.5);
color: var(--vp-c-text-3);
}
.auth-panel button {
width: 100%;
padding: 12px 24px;
padding: 0.75rem 1.5rem;
border: none;
border-radius: 8px;
background: linear-gradient(135deg, #4caf50 0%, #45a049 100%);
color: white;
border-radius: 6px;
background: var(--vp-c-brand);
color: var(--vp-c-bg);
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
transition: all 0.2s ease;
}
.auth-panel button:disabled {
@@ -237,101 +243,97 @@ onUnmounted(() => {
.auth-panel button:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(76, 175, 80, 0.3);
box-shadow: 0 4px 12px rgba(var(--vp-c-brand-rgb), 0.3);
}
.totp-display {
background: rgba(0, 0, 0, 0.3);
border-radius: 8px;
padding: 16px;
background: var(--vp-c-bg-alt);
border: 1px solid var(--vp-c-divider);
border-radius: 6px;
padding: 1rem;
text-align: center;
margin-bottom: 16px;
margin-bottom: 1rem;
}
.totp-code {
display: block;
font-size: 2.5rem;
font-weight: 700;
font-family: monospace;
font-family: var(--vp-font-family-mono);
letter-spacing: 0.2em;
margin-bottom: 8px;
margin-bottom: 0.5rem;
color: var(--vp-c-text-1);
}
.totp-timer {
height: 4px;
background: rgba(255, 255, 255, 0.2);
background: var(--vp-c-bg-soft);
border-radius: 2px;
overflow: hidden;
}
.timer-bar {
height: 100%;
background: linear-gradient(90deg, #4caf50 0%, #8bc34a 100%);
background: var(--vp-c-brand);
transition: width 0.1s linear;
}
.success-message {
background: rgba(76, 175, 80, 0.2);
border: 1px solid rgba(76, 175, 80, 0.5);
border-radius: 12px;
padding: 32px;
background: var(--vp-c-brand-soft);
border: 1px solid var(--vp-c-brand);
border-radius: 8px;
padding: 2rem;
text-align: center;
margin-bottom: 20px;
margin-bottom: 1.25rem;
}
.success-icon {
font-size: 4rem;
margin-bottom: 16px;
margin-bottom: 1rem;
}
.success-message h5 {
margin: 0 0 8px 0;
margin: 0 0 0.5rem 0;
font-size: 1.5rem;
font-weight: 700;
color: var(--vp-c-text-1);
}
.success-message p {
margin: 0 0 20px 0;
opacity: 0.8;
margin: 0 0 1.25rem 0;
color: var(--vp-c-text-2);
}
.success-message button {
padding: 12px 32px;
border: none;
border-radius: 8px;
background: rgba(255, 255, 255, 0.2);
color: white;
padding: 0.75rem 2rem;
border: 1px solid var(--vp-c-divider);
border-radius: 6px;
background: var(--vp-c-bg);
color: var(--vp-c-text-1);
font-size: 1rem;
cursor: pointer;
transition: all 0.3s ease;
transition: all 0.2s ease;
font-weight: 600;
}
.success-message button:hover {
background: rgba(255, 255, 255, 0.3);
background: var(--vp-c-bg-alt);
transform: translateY(-2px);
}
.security-tips {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 12px;
padding: 20px;
.info-box {
padding: 0.75rem;
background: var(--vp-c-bg-alt);
border: 1px solid var(--vp-c-divider);
border-left: 4px solid var(--vp-c-brand);
border-radius: 6px;
font-size: 0.9rem;
line-height: 1.6;
color: var(--vp-c-text-2);
}
.security-tips h5 {
margin: 0 0 12px 0;
font-size: 1rem;
}
.security-tips ul {
list-style: none;
padding: 0;
margin: 0;
}
.security-tips li {
padding: 6px 0;
font-size: 0.85rem;
opacity: 0.9;
.info-box strong {
color: var(--vp-c-text-1);
}
@media (max-width: 768px) {
@@ -342,9 +344,5 @@ onUnmounted(() => {
.step-arrow {
transform: rotate(90deg);
}
.lifecycle-container {
grid-template-columns: 1fr;
}
}
</style>
@@ -2,60 +2,66 @@
<div class="permission-hierarchy-demo">
<div class="demo-header">
<h4>权限层级结构</h4>
<p class="demo-desc">点击层级查看详细权限范围</p>
<p class="intro-text">点击层级查看详细权限范围</p>
</div>
<div class="hierarchy-container">
<div
v-for="(level, index) in hierarchyLevels"
:key="index"
class="level-row"
:class="{ active: selectedLevel === index }"
@click="selectLevel(index)"
>
<div class="level-icon">{{ level.icon }}</div>
<div class="level-content">
<span class="level-name">{{ level.name }}</span>
<span class="level-scope">{{ level.scope }}</span>
<div class="demo-content">
<div class="hierarchy-container">
<div
v-for="(level, index) in hierarchyLevels"
:key="index"
class="level-row"
:class="{ active: selectedLevel === index }"
@click="selectLevel(index)"
>
<div class="level-icon">{{ level.icon }}</div>
<div class="level-content">
<span class="level-name">{{ level.name }}</span>
<span class="level-scope">{{ level.scope }}</span>
</div>
<div class="permission-badges">
<span
v-for="(perm, i) in level.permissions.slice(0, 3)"
:key="i"
class="badge"
>
{{ perm }}
</span>
<span v-if="level.permissions.length > 3" class="badge more">
+{{ level.permissions.length - 3 }}
</span>
</div>
</div>
<div class="permission-badges">
<span
v-for="(perm, i) in level.permissions.slice(0, 3)"
:key="i"
class="badge"
>
{{ perm }}
</span>
<span v-if="level.permissions.length > 3" class="badge more">
+{{ level.permissions.length - 3 }}
</span>
</div>
<div class="detail-panel" v-if="selectedLevelData">
<h5>{{ selectedLevelData.name }} 详情</h5>
<div class="detail-section">
<span class="label">权限范围:</span>
<span class="value">{{ selectedLevelData.scope }}</span>
</div>
<div class="detail-section">
<span class="label">典型场景:</span>
<span class="value">{{ selectedLevelData.scenario }}</span>
</div>
<div class="detail-section permissions-list">
<span class="label">拥有权限:</span>
<div class="permissions-grid">
<span
v-for="(perm, i) in selectedLevelData.permissions"
:key="i"
class="perm-tag"
:class="perm.type"
>
{{ perm.name }}
</span>
</div>
</div>
</div>
</div>
<div class="detail-panel" v-if="selectedLevelData">
<h5>{{ selectedLevelData.name }} 详情</h5>
<div class="detail-section">
<span class="label">权限范围:</span>
<span class="value">{{ selectedLevelData.scope }}</span>
</div>
<div class="detail-section">
<span class="label">典型场景:</span>
<span class="value">{{ selectedLevelData.scenario }}</span>
</div>
<div class="detail-section permissions-list">
<span class="label">拥有权限:</span>
<div class="permissions-grid">
<span
v-for="(perm, i) in selectedLevelData.permissions"
:key="i"
class="perm-tag"
:class="perm.type"
>
{{ perm.name }}
</span>
</div>
</div>
<div class="info-box">
<strong>💡 最小权限原则</strong>始终授予用户完成工作所需的最小权限从低权限开始根据实际需求逐步提升而不是一开始就授予高权限
</div>
</div>
</template>
@@ -141,52 +147,59 @@ function selectLevel(index) {
<style scoped>
.permission-hierarchy-demo {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 16px;
padding: 24px;
color: white;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg-soft);
border-radius: 8px;
padding: 1.5rem;
margin: 1rem 0;
max-height: 600px;
overflow-y: auto;
}
.demo-header {
text-align: center;
margin-bottom: 24px;
margin-bottom: 1rem;
}
.demo-header h4 {
margin: 0 0 8px 0;
font-size: 1.4rem;
margin: 0 0 0.5rem 0;
font-weight: 800;
color: var(--vp-c-text-1);
}
.demo-desc {
.intro-text {
margin: 0;
opacity: 0.9;
color: var(--vp-c-text-2);
font-size: 0.9rem;
}
.demo-content {
margin-bottom: 1rem;
}
.hierarchy-container {
display: flex;
flex-direction: column;
gap: 8px;
margin-bottom: 24px;
gap: 0.5rem;
margin-bottom: 1.5rem;
}
.level-row {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 16px;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 10px;
gap: 0.75rem;
padding: 0.75rem 1rem;
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
border-radius: 6px;
cursor: pointer;
transition: all 0.3s ease;
transition: all 0.2s ease;
}
.level-row:hover,
.level-row.active {
background: rgba(255, 255, 255, 0.2);
transform: translateX(8px);
background: var(--vp-c-bg-alt);
border-color: var(--vp-c-brand);
transform: translateX(4px);
}
.level-icon {
@@ -196,8 +209,9 @@ function selectLevel(index) {
display: flex;
align-items: center;
justify-content: center;
background: rgba(255, 255, 255, 0.15);
border-radius: 10px;
background: var(--vp-c-bg-alt);
border: 1px solid var(--vp-c-divider);
border-radius: 6px;
}
.level-content {
@@ -209,64 +223,70 @@ function selectLevel(index) {
.level-name {
font-weight: 600;
font-size: 0.95rem;
color: var(--vp-c-text-1);
}
.level-scope {
font-size: 0.75rem;
opacity: 0.8;
color: var(--vp-c-text-3);
}
.permission-badges {
display: flex;
gap: 4px;
gap: 0.25rem;
flex-wrap: wrap;
justify-content: flex-end;
max-width: 150px;
}
.badge {
padding: 2px 8px;
background: rgba(255, 255, 255, 0.2);
border-radius: 12px;
padding: 0.125rem 0.5rem;
background: var(--vp-c-bg-alt);
border: 1px solid var(--vp-c-divider);
border-radius: 4px;
font-size: 0.65rem;
white-space: nowrap;
color: var(--vp-c-text-2);
}
.badge.more {
background: rgba(255, 255, 255, 0.4);
background: var(--vp-c-brand-soft);
border-color: var(--vp-c-brand);
color: var(--vp-c-brand-1);
}
.detail-panel {
background: rgba(255, 255, 255, 0.95);
border-radius: 12px;
padding: 20px;
color: #333;
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
padding: 1rem;
}
.detail-panel h5 {
margin: 0 0 16px 0;
margin: 0 0 1rem 0;
font-size: 1.1rem;
color: #667eea;
padding-bottom: 8px;
border-bottom: 2px solid #eee;
font-weight: 700;
color: var(--vp-c-brand-1);
padding-bottom: 0.5rem;
border-bottom: 1px solid var(--vp-c-divider);
}
.detail-section {
margin-bottom: 12px;
margin-bottom: 0.75rem;
display: flex;
align-items: flex-start;
gap: 8px;
gap: 0.5rem;
}
.detail-section .label {
font-weight: 600;
color: #666;
color: var(--vp-c-text-2);
min-width: 80px;
font-size: 0.85rem;
}
.detail-section .value {
color: #333;
color: var(--vp-c-text-1);
font-size: 0.9rem;
flex: 1;
}
@@ -274,69 +294,86 @@ function selectLevel(index) {
.permissions-grid {
display: flex;
flex-wrap: wrap;
gap: 6px;
gap: 0.375rem;
flex: 1;
}
.perm-tag {
padding: 4px 10px;
padding: 0.25rem 0.625rem;
border-radius: 4px;
font-size: 0.7rem;
font-weight: 500;
}
.perm-tag.full {
background: #f44336;
color: white;
background: rgba(var(--vp-c-brand-delta-rgb), 0.15);
color: var(--vp-c-brand-delta);
}
.perm-tag.read,
.perm-tag.user,
.perm-tag.readonly {
background: #4caf50;
color: white;
background: var(--vp-c-brand-soft);
color: var(--vp-c-brand-1);
}
.perm-tag.limited,
.perm-tag.role,
.perm-tag.limited {
background: #ff9800;
color: white;
.perm-tag.role {
background: rgba(var(--vp-c-brand-rgb), 0.1);
color: var(--vp-c-brand);
}
.perm-tag.deny,
.perm-tag.critical {
background: #9c27b0;
color: white;
background: rgba(var(--vp-c-brand-delta-rgb), 0.15);
color: var(--vp-c-brand-delta);
}
.perm-tag.temp,
.perm-tag.conditional,
.perm-tag.service {
background: #2196f3;
color: white;
background: var(--vp-c-bg-soft);
border: 1px solid var(--vp-c-divider);
color: var(--vp-c-text-1);
}
.perm-tag.admin,
.perm-tag.org,
.perm-tag.billing {
background: #673ab7;
color: white;
background: rgba(var(--vp-c-brand-rgb), 0.15);
color: var(--vp-c-brand);
}
.perm-tag.api,
.perm-tag.programmatic,
.perm-tag.security {
background: #607d8b;
color: white;
background: var(--vp-c-bg-alt);
border: 1px solid var(--vp-c-divider);
color: var(--vp-c-text-2);
}
.perm-tag.cross,
.perm-tag.secure,
.perm-tag.audit,
.perm-tag.policy {
background: #795548;
color: white;
background: var(--vp-c-bg-soft);
border: 1px solid var(--vp-c-divider);
color: var(--vp-c-text-2);
}
.info-box {
padding: 0.75rem;
background: var(--vp-c-bg-alt);
border: 1px solid var(--vp-c-divider);
border-left: 4px solid var(--vp-c-brand);
border-radius: 6px;
font-size: 0.9rem;
line-height: 1.6;
color: var(--vp-c-text-2);
}
.info-box strong {
color: var(--vp-c-text-1);
}
@media (max-width: 768px) {
@@ -348,12 +385,12 @@ function selectLevel(index) {
width: 100%;
justify-content: flex-start;
max-width: none;
margin-top: 8px;
margin-top: 0.5rem;
}
.detail-section {
flex-direction: column;
gap: 4px;
gap: 0.25rem;
}
}
</style>
@@ -2,79 +2,85 @@
<div class="role-policy-demo">
<div class="demo-header">
<h4>角色与策略关系可视化</h4>
<p class="demo-desc">拖动查看角色如何关联多个策略</p>
<p class="intro-text">拖动查看角色如何关联多个策略</p>
</div>
<div class="visualization-container">
<!-- Central Role -->
<div class="central-role">
<div class="role-core" @click="toggleRoleDetails"
:class="{ expanded: showRoleDetails }">
<div class="role-icon">🎭</div>
<div class="role-info">
<span class="role-name">{{ roleName }}</span>
<span class="role-type">{{ roleType }}</span>
<div class="demo-content">
<div class="visualization-container">
<!-- Central Role -->
<div class="central-role">
<div class="role-core" @click="toggleRoleDetails"
:class="{ expanded: showRoleDetails }">
<div class="role-icon">🎭</div>
<div class="role-info">
<span class="role-name">{{ roleName }}</span>
<span class="role-type">{{ roleType }}</span>
</div>
<div class="expand-icon">{{ showRoleDetails ? '▼' : '▶' }}</div>
</div>
<!-- Trust Policy -->
<div class="trust-policy" v-if="showRoleDetails">
<div class="policy-header">
<span class="policy-icon">🔐</span>
<span class="policy-title">信任策略 (Trust Policy)</span>
</div>
<div class="policy-content">
<div class="policy-item" v-for="(trust, i) in trustPolicy" :key="i">
<span class="principal">{{ trust.principal }}</span>
<span class="action">可执行: {{ trust.action }}</span>
<span class="condition" v-if="trust.condition">条件: {{ trust.condition }}</span>
</div>
</div>
</div>
<div class="expand-icon">{{ showRoleDetails ? '▼' : '▶' }}</div>
</div>
<!-- Trust Policy -->
<div class="trust-policy" v-if="showRoleDetails">
<div class="policy-header">
<span class="policy-icon">🔐</span>
<span class="policy-title">信任策略 (Trust Policy)</span>
</div>
<div class="policy-content">
<div class="policy-item" v-for="(trust, i) in trustPolicy" :key="i">
<span class="principal">{{ trust.principal }}</span>
<span class="action">可执行: {{ trust.action }}</span>
<span class="condition" v-if="trust.condition">条件: {{ trust.condition }}</span>
<!-- Connection Lines (SVG) -->
<svg class="connection-lines" v-if="mounted">
<line
v-for="(line, index) in connectionLines"
:key="index"
:x1="line.x1"
:y1="line.y1"
:x2="line.x2"
:y2="line.y2"
:class="['connection-line', line.type, { active: hoveredPolicy === line.policyIndex }]"
@mouseenter="hoveredPolicy = line.policyIndex"
@mouseleave="hoveredPolicy = null"
/>
</svg>
<!-- Attached Policies -->
<div class="attached-policies">
<div
v-for="(policy, index) in attachedPolicies"
:key="index"
class="policy-card"
:class="{ active: hoveredPolicy === index, selected: selectedPolicy === index }"
:style="getPolicyPosition(index)"
@mouseenter="hoveredPolicy = index"
@mouseleave="hoveredPolicy = null"
@click="selectPolicy(index)"
>
<div class="policy-header">
<span class="policy-icon">{{ policy.icon }}</span>
<span class="policy-name">{{ policy.name }}</span>
</div>
<div class="policy-permissions" v-if="selectedPolicy === index">
<div class="permission-item" v-for="(perm, i) in policy.permissions" :key="i">
<span class="perm-effect" :class="perm.effect">{{ perm.effect }}</span>
<span class="perm-action">{{ perm.action }}</span>
<span class="perm-resource">{{ perm.resource }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Connection Lines (SVG) -->
<svg class="connection-lines" v-if="mounted">
<line
v-for="(line, index) in connectionLines"
:key="index"
:x1="line.x1"
:y1="line.y1"
:x2="line.x2"
:y2="line.y2"
:class="['connection-line', line.type, { active: hoveredPolicy === line.policyIndex }]"
@mouseenter="hoveredPolicy = line.policyIndex"
@mouseleave="hoveredPolicy = null"
/>
</svg>
<!-- Attached Policies -->
<div class="attached-policies">
<div
v-for="(policy, index) in attachedPolicies"
:key="index"
class="policy-card"
:class="{ active: hoveredPolicy === index, selected: selectedPolicy === index }"
:style="getPolicyPosition(index)"
@mouseenter="hoveredPolicy = index"
@mouseleave="hoveredPolicy = null"
@click="selectPolicy(index)"
>
<div class="policy-header">
<span class="policy-icon">{{ policy.icon }}</span>
<span class="policy-name">{{ policy.name }}</span>
</div>
<div class="policy-permissions" v-if="selectedPolicy === index">
<div class="permission-item" v-for="(perm, i) in policy.permissions" :key="i">
<span class="perm-effect" :class="perm.effect">{{ perm.effect }}</span>
<span class="perm-action">{{ perm.action }}</span>
<span class="perm-resource">{{ perm.resource }}</span>
</div>
</div>
</div>
</div>
<div class="info-box">
<strong>💡 策略叠加</strong>一个角色可以附加多个策略最终的权限是所有策略的叠加结果Deny 策略优先级高于 Allow
</div>
</div>
</template>
@@ -137,10 +143,6 @@ function selectPolicy(index) {
selectedPolicy.value = index
}
function selectFeature(platform, index) {
// For compatibility with other demos
}
function getPolicyPosition(index) {
const positions = [
{ top: '0%', right: '0%' },
@@ -151,7 +153,6 @@ function getPolicyPosition(index) {
}
function calculateConnections() {
// Simplified connection calculation
connectionLines.value = attachedPolicies.value.map((_, index) => ({
x1: 50,
y1: 50,
@@ -177,30 +178,35 @@ onUnmounted(() => {
<style scoped>
.role-policy-demo {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 16px;
padding: 24px;
color: white;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
min-height: 600px;
border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg-soft);
border-radius: 8px;
padding: 1.5rem;
margin: 1rem 0;
max-height: 600px;
overflow-y: auto;
}
.demo-header {
text-align: center;
margin-bottom: 24px;
margin-bottom: 1rem;
}
.demo-header h4 {
margin: 0 0 8px 0;
font-size: 1.4rem;
margin: 0 0 0.5rem 0;
font-weight: 800;
color: var(--vp-c-text-1);
}
.demo-desc {
.intro-text {
margin: 0;
opacity: 0.9;
color: var(--vp-c-text-2);
font-size: 0.9rem;
}
.demo-content {
margin-bottom: 1rem;
}
.visualization-container {
position: relative;
min-height: 500px;
@@ -217,27 +223,28 @@ onUnmounted(() => {
}
.role-core {
background: rgba(255, 255, 255, 0.95);
border-radius: 16px;
padding: 20px;
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
padding: 1.25rem;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
transition: all 0.2s ease;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.role-core:hover {
transform: scale(1.02);
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.3);
border-color: var(--vp-c-brand);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.role-core.expanded {
border-radius: 16px 16px 0 0;
border-radius: 8px 8px 0 0;
}
.role-icon {
font-size: 2.5rem;
text-align: center;
margin-bottom: 8px;
margin-bottom: 0.5rem;
}
.role-info {
@@ -246,38 +253,39 @@ onUnmounted(() => {
.role-name {
display: block;
color: #333;
color: var(--vp-c-text-1);
font-weight: 700;
font-size: 1rem;
margin-bottom: 4px;
margin-bottom: 0.25rem;
}
.role-type {
display: block;
color: #666;
color: var(--vp-c-text-2);
font-size: 0.8rem;
}
.expand-icon {
text-align: center;
margin-top: 8px;
color: #999;
margin-top: 0.5rem;
color: var(--vp-c-text-3);
font-size: 0.8rem;
}
/* Trust Policy */
.trust-policy {
background: rgba(255, 255, 255, 0.95);
border-radius: 0 0 16px 16px;
padding: 16px 20px;
border-top: 1px solid rgba(0, 0, 0, 0.1);
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
border-top: none;
border-radius: 0 0 8px 8px;
padding: 1rem 1.25rem;
}
.policy-header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 12px;
gap: 0.5rem;
margin-bottom: 0.75rem;
}
.policy-icon {
@@ -286,37 +294,38 @@ onUnmounted(() => {
.policy-title {
font-weight: 700;
color: #333;
color: var(--vp-c-text-1);
font-size: 0.85rem;
}
.policy-content {
display: flex;
flex-direction: column;
gap: 8px;
gap: 0.5rem;
}
.policy-item {
background: rgba(102, 126, 234, 0.1);
background: var(--vp-c-bg-alt);
border: 1px solid var(--vp-c-divider);
border-radius: 6px;
padding: 8px;
padding: 0.5rem;
font-size: 0.75rem;
display: flex;
flex-direction: column;
gap: 2px;
gap: 0.125rem;
}
.principal {
font-weight: 600;
color: #667eea;
color: var(--vp-c-brand-1);
}
.action {
color: #4caf50;
color: var(--vp-c-text-2);
}
.condition {
color: #ff9800;
color: var(--vp-c-text-3);
}
/* Connection Lines SVG */
@@ -331,20 +340,20 @@ onUnmounted(() => {
}
.connection-line {
stroke: rgba(255, 255, 255, 0.3);
stroke: var(--vp-c-divider);
stroke-width: 2;
fill: none;
pointer-events: stroke;
cursor: pointer;
transition: all 0.3s ease;
transition: all 0.2s ease;
}
.connection-line.allow {
stroke: #4caf50;
stroke: var(--vp-c-brand);
}
.connection-line.deny {
stroke: #f44336;
stroke: var(--vp-c-brand-delta);
stroke-dasharray: 5, 5;
}
@@ -364,31 +373,34 @@ onUnmounted(() => {
}
.policy-card {
background: rgba(255, 255, 255, 0.95);
border-radius: 12px;
padding: 16px;
margin-bottom: 12px;
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
padding: 1rem;
margin-bottom: 0.75rem;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
transition: all 0.2s ease;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
position: relative;
}
.policy-card:hover,
.policy-card.active {
transform: translateX(-8px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
border-color: var(--vp-c-brand);
transform: translateX(-4px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.policy-card.selected {
border: 2px solid #667eea;
border-color: var(--vp-c-brand);
background: var(--vp-c-bg-alt);
}
.policy-card .policy-header {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 8px;
gap: 0.625rem;
margin-bottom: 0.5rem;
}
.policy-card .policy-icon {
@@ -397,29 +409,29 @@ onUnmounted(() => {
.policy-card .policy-name {
font-weight: 700;
color: #333;
color: var(--vp-c-text-1);
font-size: 0.9rem;
}
.policy-permissions {
margin-top: 12px;
padding-top: 12px;
border-top: 1px solid rgba(0, 0, 0, 0.1);
margin-top: 0.75rem;
padding-top: 0.75rem;
border-top: 1px solid var(--vp-c-divider);
}
.permission-item {
display: flex;
align-items: center;
gap: 8px;
padding: 6px;
margin-bottom: 4px;
background: rgba(0, 0, 0, 0.03);
gap: 0.5rem;
padding: 0.375rem;
margin-bottom: 0.25rem;
background: var(--vp-c-bg-alt);
border-radius: 4px;
font-size: 0.7rem;
}
.perm-effect {
padding: 2px 6px;
padding: 0.125rem 0.375rem;
border-radius: 3px;
font-weight: 600;
font-size: 0.65rem;
@@ -427,33 +439,48 @@ onUnmounted(() => {
}
.perm-effect.Allow {
background: #4caf50;
color: white;
background: var(--vp-c-brand-soft);
color: var(--vp-c-brand-1);
}
.perm-effect.Deny {
background: #f44336;
color: white;
background: rgba(var(--vp-c-brand-delta-rgb), 0.15);
color: var(--vp-c-brand-delta);
}
.perm-action {
font-family: monospace;
color: #667eea;
font-family: var(--vp-font-family-mono);
color: var(--vp-c-brand-1);
}
.perm-resource {
color: #999;
color: var(--vp-c-text-3);
font-size: 0.6rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.info-box {
padding: 0.75rem;
background: var(--vp-c-bg-alt);
border: 1px solid var(--vp-c-divider);
border-left: 4px solid var(--vp-c-brand);
border-radius: 6px;
font-size: 0.9rem;
line-height: 1.6;
color: var(--vp-c-text-2);
}
.info-box strong {
color: var(--vp-c-text-1);
}
@media (max-width: 1024px) {
.visualization-container {
display: flex;
flex-direction: column;
gap: 20px;
gap: 1rem;
}
.central-role,