feat: K8s 部署清单、Token 接入加固与日志/设置页优化
- kubernetes/: 30Gi PVC、单副本 Recreate、Secret/Service/NodePort/Ingress 清单与部署文档 - 数据安全: kubeconfig/token 统一加密存储,启动迁移历史明文,DB 0600 - Token 接入: 命名空间级最小权限提示、长期 Token、自签证书 TLS 兼容 - 部署: 命名空间级 Token 下跳过 Namespace patch/枚举,节点信息容错 - 日志页: namespace → app → pod 选择顺序与作用域收窄 - 设置页: 拆分为通用/告警/LDAP/镜像源四个 tab - 弹框: 统一 max-h 滚动与长命令换行,修复内容溢出
This commit is contained in:
1 parent
2d73661dd1
commit
9f7c522157
50 files changed
+1346
-149
No files matched your search
@@ -1,4 +1,5 @@
|
||||
import { memo, useEffect, useState } from 'react'
|
||||
import { Link } from 'react-router'
|
||||
import { Play, Square, RotateCw, Trash2, Terminal, ExternalLink, Pencil, Rocket } from 'lucide-react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { StatusBadge } from '@/components/StatusBadge'
|
||||
@@ -91,7 +92,12 @@ export const DeploymentCard = memo(function DeploymentCard({
|
||||
</a>
|
||||
) }] : []),
|
||||
]}
|
||||
message={d.statusMessage || undefined}
|
||||
message={
|
||||
d.statusMessage ||
|
||||
(d.status === 'running'
|
||||
? '提示:到「日志查询 → 数据源」关联 Loki 后即可查询;跨集群/公网访问需先为 Loki 配置 Ingress 域名。'
|
||||
: undefined)
|
||||
}
|
||||
actions={
|
||||
<>
|
||||
<Button variant="outline" size="sm" className="text-xs" onClick={() => onEditRetention(d)} disabled={isDeploying || isBusy}>
|
||||
@@ -115,6 +121,16 @@ export const DeploymentCard = memo(function DeploymentCard({
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
{d.status === 'running' && (
|
||||
<>
|
||||
<Button variant="outline" size="sm" className="text-xs" asChild>
|
||||
<Link to="/logs">日志查询</Link>
|
||||
</Button>
|
||||
<Button variant="outline" size="sm" className="text-xs" asChild>
|
||||
<Link to="/collectors">采集器</Link>
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
<Button variant="outline" size="sm" className="text-xs" onClick={() => onViewLogs(d)}>
|
||||
<Terminal className="size-3 mr-1" />日志
|
||||
</Button>
|
||||
|
||||
@@ -347,7 +347,7 @@ export const DeploymentFormDialog = memo(function DeploymentFormDialog({
|
||||
<FormItem>
|
||||
<FormLabel>存储容量</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="10Gi" {...field} />
|
||||
<Input placeholder="20Gi" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -363,7 +363,7 @@ export const DeploymentFormDialog = memo(function DeploymentFormDialog({
|
||||
<FormItem>
|
||||
<FormLabel>CPU</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="500m" {...field} />
|
||||
<Input placeholder="1" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -376,7 +376,7 @@ export const DeploymentFormDialog = memo(function DeploymentFormDialog({
|
||||
<FormItem>
|
||||
<FormLabel>内存</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="512Mi" {...field} />
|
||||
<Input placeholder="2Gi" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
|
||||
@@ -98,7 +98,7 @@ export const RetentionDialog = memo(function RetentionDialog({ deployment, onClo
|
||||
{liveConfig && (
|
||||
<div>
|
||||
<p className="mb-2 text-xs text-muted-foreground">{liveConfig.location}</p>
|
||||
<pre className="max-h-[45vh] overflow-auto rounded-md bg-muted/50 p-3 font-mono text-xs whitespace-pre">{liveConfig.yaml}</pre>
|
||||
<pre className="max-h-[45vh] max-w-full overflow-auto rounded-md bg-muted/50 p-3 font-mono text-xs whitespace-pre">{liveConfig.yaml}</pre>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -15,6 +15,17 @@ const isPositiveInt = (v: string, min: number, max?: number) => {
|
||||
return Number.isInteger(n) && n >= min && (max === undefined || n <= max)
|
||||
}
|
||||
|
||||
// 解析存储容量为 GiB(支持 Gi/Mi/Ti),无法解析返回 null
|
||||
const parseGiB = (v: string): number | null => {
|
||||
const m = /^(\d+(?:\.\d+)?)\s*(Gi|Mi|Ti)$/i.exec(v.trim())
|
||||
if (!m) return null
|
||||
const n = Number(m[1])
|
||||
const unit = m[2].toLowerCase()
|
||||
if (unit === 'ti') return n * 1024
|
||||
if (unit === 'mi') return n / 1024
|
||||
return n
|
||||
}
|
||||
|
||||
export const deploymentFormSchema = z
|
||||
.object({
|
||||
name: z.string().min(1, '请输入部署名称'),
|
||||
@@ -36,7 +47,7 @@ export const deploymentFormSchema = z
|
||||
.refine((v) => isPositiveInt(v, 1, 10), '副本数需为 1-10 之间的整数'),
|
||||
resourcesCPU: z.string().min(1, '请填写 CPU 配额(如 500m)'),
|
||||
resourcesMem: z.string().min(1, '请填写内存配额(如 512Mi)'),
|
||||
storageSize: z.string().min(1, '请填写存储容量(如 10Gi)'),
|
||||
storageSize: z.string().min(1, '请填写存储容量(如 20Gi)'),
|
||||
})
|
||||
.superRefine((data, ctx) => {
|
||||
if (isK8sMode(data.mode) && !data.clusterID) {
|
||||
@@ -51,6 +62,12 @@ export const deploymentFormSchema = z
|
||||
if (isK8sMode(data.mode) && !data.namespace.trim()) {
|
||||
ctx.addIssue({ code: 'custom', path: ['namespace'], message: '请填写命名空间' })
|
||||
}
|
||||
if (isK8sMode(data.mode)) {
|
||||
const gi = parseGiB(data.storageSize)
|
||||
if (gi === null || gi < 20) {
|
||||
ctx.addIssue({ code: 'custom', path: ['storageSize'], message: '阿里云 ESSD 最小 20Gi,请填 20Gi 或以上' })
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export type DeploymentFormValues = z.infer<typeof deploymentFormSchema>
|
||||
@@ -69,7 +86,7 @@ export const DEFAULT_VALUES: DeploymentFormValues = {
|
||||
authEnabled: false,
|
||||
retentionDays: '30',
|
||||
replicas: '1',
|
||||
resourcesCPU: '500m',
|
||||
resourcesMem: '512Mi',
|
||||
storageSize: '10Gi',
|
||||
resourcesCPU: '1',
|
||||
resourcesMem: '2Gi',
|
||||
storageSize: '20Gi',
|
||||
}
|
||||
Reference in new issue
Block a user