Files
sanbuphy 0eba9e87e9 fix(eslint): reduce warnings in GitHub Actions deployment
- Disable formatting rules (handled by Prettier)
- Relaxed strict Vue/JS rules for demo code compatibility
- Fix syntax errors in ApiPlayground and VoiceCloningDemo
- Fix duplicate else-if condition in ApiPlayground
- Fix Promise executor async pattern in AutoregressiveAudioDemo
- Add TypeScript file support to ESLint config

Warnings reduced from 295 to 251 problems.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-18 17:38:10 +08:00

517 lines
12 KiB
Vue
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="kubernetes-demo">
<div class="demo-header">
<h4> Kubernetes 编排演示</h4>
<p>观察 K8s 如何自动调度容器实现负载均衡和故障恢复</p>
</div>
<div class="k8s-architecture">
<div class="control-plane">
<div class="plane-title">
控制平面 (Control Plane)
</div>
<div class="components">
<div
v-for="comp in controlPlane"
:key="comp.name"
class="component"
:class="{ active: activeComponent === comp.name }"
@click="activeComponent = comp.name"
>
<div class="comp-icon">
{{ comp.icon }}
</div>
<div class="comp-name">
{{ comp.name }}
</div>
<div class="comp-desc">
{{ comp.desc }}
</div>
</div>
</div>
</div>
<div class="worker-nodes">
<div class="plane-title">
工作节点 (Worker Nodes)
</div>
<div class="nodes-container">
<div
v-for="node in workerNodes"
:key="node.name"
class="node"
:class="{
active: node.status === 'active',
failed: node.status === 'failed',
selected: selectedNode === node.name
}"
@click="selectNode(node.name)"
>
<div class="node-header">
<span class="node-icon">{{ node.icon }}</span>
<span class="node-name">{{ node.name }}</span>
<span
class="node-status"
:class="node.status"
>{{ node.statusText }}</span>
</div>
<div class="node-resources">
<div class="resource">
<span class="res-label">CPU:</span>
<div class="res-bar">
<div
class="res-fill"
:style="{ width: node.cpu + '%' }"
:class="{ high: node.cpu > 80 }"
/>
</div>
<span class="res-value">{{ node.cpu }}%</span>
</div>
<div class="resource">
<span class="res-label">内存:</span>
<div class="res-bar">
<div
class="res-fill"
:style="{ width: node.memory + '%' }"
:class="{ high: node.memory > 80 }"
/>
</div>
<span class="res-value">{{ node.memory }}%</span>
</div>
</div>
<div class="node-pods">
<div class="pods-label">
运行 Pod: {{ node.pods }}
</div>
<div class="pods-grid">
<div
v-for="n in Math.min(node.pods, 8)"
:key="n"
class="pod-dot"
:class="{
running: node.status === 'active',
pending: node.status === 'pending',
failed: node.status === 'failed'
}"
/>
<div
v-if="node.pods > 8"
class="pod-more"
>
+{{ node.pods - 8 }}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="k8s-controls">
<button
class="control-btn"
:disabled="isScheduling"
@click="simulateScheduling"
>
{{ isScheduling ? '调度中...' : '🚀 模拟 Pod 调度' }}
</button>
<button
class="control-btn"
:disabled="isScaling"
@click="simulateScaling"
>
{{ isScaling ? '扩容中...' : '📈 自动扩容' }}
</button>
<button
class="control-btn danger"
:disabled="isFailing"
@click="simulateFailure"
>
{{ isFailing ? '故障注入中...' : '💥 模拟节点故障' }}
</button>
<button
class="control-btn"
@click="resetCluster"
>
🔄 重置集群
</button>
</div>
<div
v-if="logs.length > 0"
class="k8s-logs"
>
<div
v-for="(log, idx) in logs.slice(-5)"
:key="idx"
class="log-entry"
:class="log.level"
>
<span class="log-time">{{ log.time }}</span>
<span class="log-message">{{ log.message }}</span>
</div>
</div>
<div class="demo-explanation">
<h5>💡 Kubernetes 核心概念</h5>
<ul>
<li><strong>Pod</strong>最小的部署单元一个 Pod 可以包含一个或多个容器</li>
<li><strong>Deployment</strong>管理 Pod 的副本数量和滚动更新</li>
<li><strong>Service</strong>提供稳定的网络访问入口实现负载均衡</li>
<li><strong>Scheduler</strong>根据资源需求和策略自动将 Pod 调度到合适的节点</li>
</ul>
</div>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
const controlPlane = [
{ name: 'API Server', icon: '🌐', desc: '集群的统一入口' },
{ name: 'etcd', icon: '🗄️', desc: '分布式键值存储' },
{ name: 'Scheduler', icon: '📋', desc: 'Pod 调度器' },
{ name: 'Controller', icon: '🎮', desc: '控制器管理器' }
]
const workerNodes = reactive([
{
name: 'Node-1',
icon: '🖥️',
status: 'active',
statusText: '运行中',
cpu: 45,
memory: 60,
pods: 5
},
{
name: 'Node-2',
icon: '🖥️',
status: 'active',
statusText: '运行中',
cpu: 30,
memory: 40,
pods: 3
},
{
name: 'Node-3',
icon: '🖥️',
status: 'pending',
statusText: '准备中',
cpu: 0,
memory: 0,
pods: 0
}
])
const activeComponent = ref(null)
const selectedNode = ref(null)
const isScheduling = ref(false)
const isScaling = ref(false)
const isFailing = ref(false)
const logs = ref([])
const addLog = (message, level = 'info') => {
const now = new Date()
const time = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}:${now.getSeconds().toString().padStart(2, '0')}`
logs.value.push({ time, message, level })
if (logs.value.length > 20) logs.value.shift()
}
const selectNode = (name) => {
selectedNode.value = selectedNode.value === name ? null : name
}
const simulateScheduling = async () => {
isScheduling.value = true
addLog('开始调度新 Pod...', 'info')
await new Promise(r => setTimeout(r, 800))
addLog('Scheduler: 评估节点资源...', 'info')
await new Promise(r => setTimeout(r, 800))
const targetNode = workerNodes.find(n => n.status === 'active' && n.cpu < 70)
if (targetNode) {
targetNode.pods++
targetNode.cpu += 10
addLog(`Pod 已调度到 ${targetNode.name}`, 'success')
} else {
addLog('警告: 没有合适的节点可调度', 'warning')
}
isScheduling.value = false
}
const simulateScaling = async () => {
isScaling.value = true
addLog('检测到高负载,开始水平扩容...', 'info')
const pendingNode = workerNodes.find(n => n.status === 'pending')
if (pendingNode) {
await new Promise(r => setTimeout(r, 1500))
pendingNode.status = 'active'
pendingNode.statusText = '运行中'
pendingNode.cpu = 20
pendingNode.memory = 30
addLog(`${pendingNode.name} 已启动并加入集群`, 'success')
} else {
addLog('已达到最大节点数', 'warning')
}
isScaling.value = false
}
const simulateFailure = async () => {
isFailing.value = true
const targetNode = workerNodes.find(n => n.status === 'active')
if (targetNode) {
addLog(`警告: ${targetNode.name} 失去连接!`, 'error')
targetNode.status = 'failed'
targetNode.statusText = '故障'
await new Promise(r => setTimeout(r, 1000))
addLog('Controller: 开始重新调度 Pod...', 'info')
await new Promise(r => setTimeout(r, 1500))
const healthyNode = workerNodes.find(n => n.status === 'active' && n.name !== targetNode.name)
if (healthyNode) {
healthyNode.pods += targetNode.pods
addLog(`Pod 已成功迁移到 ${healthyNode.name}`, 'success')
}
targetNode.pods = 0
targetNode.cpu = 0
targetNode.memory = 0
}
isFailing.value = false
}
const resetCluster = () => {
workerNodes.forEach((node, index) => {
if (index < 2) {
node.status = 'active'
node.statusText = '运行中'
node.cpu = 30 + index * 15
node.memory = 40 + index * 20
node.pods = 3 + index * 2
} else {
node.status = 'pending'
node.statusText = '准备中'
node.cpu = 0
node.memory = 0
node.pods = 0
}
})
logs.value = []
addLog('集群已重置', 'info')
}
</script>
<style scoped>
.container-docker-demo {
border: 1px solid var(--vp-c-divider);
border-radius: 6px;
background: var(--vp-c-bg-soft);
padding: 1.5rem;
margin: 0.5rem 0;
}
.demo-header {
margin-bottom: 1.5rem;
}
.demo-header h4 {
margin: 0 0 0.5rem 0;
font-size: 1.1rem;
color: var(--vp-c-text-1);
}
.demo-header p {
margin: 0;
font-size: 0.9rem;
color: var(--vp-c-text-2);
}
.docker-visualization {
display: flex;
gap: 1rem;
margin-bottom: 1.5rem;
align-items: stretch;
}
.layer {
flex: 1;
background: var(--vp-c-bg);
border: 2px solid var(--vp-c-divider);
border-radius: 6px;
padding: 0.75rem;
cursor: pointer;
transition: all 0.3s;
}
.layer:hover,
.layer.active {
border-color: var(--vp-c-brand);
}
.layer h5 {
margin: 0 0 1rem 0;
text-align: center;
font-size: 0.95rem;
color: var(--vp-c-text-1);
}
.server-stack,
.docker-stack {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.layer-item {
padding: 0.6rem;
border-radius: 4px;
text-align: center;
font-size: 0.8rem;
}
.layer-item.app {
background: rgba(102, 126, 234, 0.2);
color: var(--vp-c-brand);
font-weight: 600;
}
.layer-item.deps {
background: var(--vp-c-bg-soft);
border: 1px dashed var(--vp-c-divider);
}
.layer-item.os,
.layer-item.hardware {
background: var(--vp-c-bg-soft);
color: var(--vp-c-text-2);
}
.layer-item.conflict {
background: rgba(239, 68, 68, 0.2);
color: var(--vp-c-danger);
font-weight: 600;
animation: pulse 1s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.containers {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 0.5rem;
}
.container-box {
background: rgba(102, 126, 234, 0.1);
border: 1px solid rgba(102, 126, 234, 0.3);
border-radius: 6px;
padding: 0.5rem;
text-align: center;
}
.container-app {
font-weight: 600;
font-size: 0.8rem;
color: var(--vp-c-brand);
margin-bottom: 0.2rem;
}
.container-deps {
font-size: 0.7rem;
color: var(--vp-c-text-2);
}
.docker-engine {
padding: 0.6rem;
background: rgba(16, 185, 129, 0.1);
border: 1px solid rgba(16, 185, 129, 0.3);
border-radius: 4px;
text-align: center;
font-size: 0.8rem;
font-weight: 600;
color: #059669;
}
.host-os,
.hardware {
padding: 0.6rem;
background: var(--vp-c-bg-soft);
border-radius: 4px;
text-align: center;
font-size: 0.8rem;
color: var(--vp-c-text-2);
}
.vs-divider {
display: flex;
align-items: center;
font-weight: 700;
color: var(--vp-c-text-3);
font-size: 0.9rem;
}
.benefits-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1rem;
}
.benefit-card {
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
border-radius: 6px;
padding: 0.75rem;
text-align: center;
transition: all 0.2s;
}
.benefit-card:hover {
border-color: var(--vp-c-brand);
transform: translateY(-2px);
}
.benefit-icon {
font-size: 2rem;
margin-bottom: 0.5rem;
}
.benefit-title {
font-weight: 600;
font-size: 0.95rem;
color: var(--vp-c-text-1);
margin-bottom: 0.25rem;
}
.benefit-desc {
font-size: 0.8rem;
color: var(--vp-c-text-2);
line-height: 1.4;
}
@media (max-width: 768px) {
.docker-visualization {
flex-direction: column;
}
.vs-divider {
justify-content: center;
padding: 0.5rem 0;
}
.benefits-grid {
grid-template-columns: 1fr;
}
}
</style>