feat: 路由改名/kubectl预装/导出YAML精简/Service标签页优化/Alpine镜像源换清华
- 路由 /deploy/credentials → /deploy/configureTools - kubectl 改为预装(Dockerfile) + 种子数据 preinstall - 导出YAML精简: Service/Ingress/Deployment 全面清理运行时字段 - DeploymentEditService 只显示匹配 selector 的关联 Service - Alpine 镜像源 mirrors.aliyun.com → mirrors.tuna.tsinghua.edu.cn
This commit is contained in:
1 parent
dd7cb6895e
commit
451f65f9c8
9 files changed
+109
-29
No files matched your search
+8
-4
@@ -35,7 +35,7 @@ ARG BUILD_TIME
|
|||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# 使用国内 Alpine 镜像源加速
|
# 使用国内 Alpine 镜像源加速
|
||||||
RUN sed -i 's|dl-cdn.alpinelinux.org|mirrors.aliyun.com|g' /etc/apk/repositories
|
RUN sed -i 's|dl-cdn.alpinelinux.org|mirrors.tuna.tsinghua.edu.cn|g' /etc/apk/repositories
|
||||||
RUN apk add --no-cache git ca-certificates tzdata
|
RUN apk add --no-cache git ca-certificates tzdata
|
||||||
|
|
||||||
ENV TZ=Asia/Shanghai
|
ENV TZ=Asia/Shanghai
|
||||||
@@ -82,14 +82,18 @@ RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w -X main.vers
|
|||||||
FROM alpine:latest
|
FROM alpine:latest
|
||||||
|
|
||||||
# 使用国内 Alpine 镜像源加速
|
# 使用国内 Alpine 镜像源加速
|
||||||
RUN sed -i 's|dl-cdn.alpinelinux.org|mirrors.aliyun.com|g' /etc/apk/repositories
|
RUN sed -i 's|dl-cdn.alpinelinux.org|mirrors.tuna.tsinghua.edu.cn|g' /etc/apk/repositories
|
||||||
|
|
||||||
# 安装运行时依赖(核心最小集,重型工具改为懒加载)// Logan.Li
|
# 安装运行时依赖(核心最小集,重型工具改为懒加载)// Logan.Li
|
||||||
# bash=kubectl交互终端, curl=健康检查, git=流水线代码拉取, docker-cli=镜像构建推送
|
# bash=kubectl交互终端, curl=健康检查, git=流水线代码拉取, docker-cli=镜像构建推送
|
||||||
# kubectl/maven/nodejs/helm → 见「发布配置→工具配置」Tab,首次使用自动下载
|
# kubectl=预装(~50MB),maven/nodejs/helm → 工具配置Tab懒加载
|
||||||
# Pod文件浏览/exec 通过 WebSocket channel 协议直连 K8s API,无需 kubectl
|
|
||||||
RUN apk --no-cache add ca-certificates tzdata bash curl git docker-cli docker-cli-buildx
|
RUN apk --no-cache add ca-certificates tzdata bash curl git docker-cli docker-cli-buildx
|
||||||
|
|
||||||
|
# kubectl 预装 // Logan.Li
|
||||||
|
ARG KUBECTL_VERSION=v1.32.0
|
||||||
|
RUN curl -sLo /usr/local/bin/kubectl https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl && \
|
||||||
|
chmod +x /usr/local/bin/kubectl
|
||||||
|
|
||||||
# Maven 阿里云镜像加速(maven 懒加载后生效)
|
# Maven 阿里云镜像加速(maven 懒加载后生效)
|
||||||
RUN mkdir -p /root/.m2 && \
|
RUN mkdir -p /root/.m2 && \
|
||||||
echo '<?xml version="1.0" encoding="UTF-8"?><settings><mirrors><mirror><id>aliyun</id><mirrorOf>central</mirrorOf><name>Aliyun Maven</name><url>https://maven.aliyun.com/repository/public</url></mirror></mirrors></settings>' > /root/.m2/settings.xml
|
echo '<?xml version="1.0" encoding="UTF-8"?><settings><mirrors><mirror><id>aliyun</id><mirrorOf>central</mirrorOf><name>Aliyun Maven</name><url>https://maven.aliyun.com/repository/public</url></mirror></mirrors></settings>' > /root/.m2/settings.xml
|
||||||
|
|||||||
+8
-3
@@ -17,7 +17,7 @@ FROM golang:1.24-alpine AS builder
|
|||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# 使用国内 Alpine 镜像源加速 // Logan.Li
|
# 使用国内 Alpine 镜像源加速 // Logan.Li
|
||||||
RUN sed -i 's|dl-cdn.alpinelinux.org|mirrors.aliyun.com|g' /etc/apk/repositories
|
RUN sed -i 's|dl-cdn.alpinelinux.org|mirrors.tuna.tsinghua.edu.cn|g' /etc/apk/repositories
|
||||||
RUN apk add --no-cache git ca-certificates tzdata
|
RUN apk add --no-cache git ca-certificates tzdata
|
||||||
|
|
||||||
ENV TZ=Asia/Shanghai
|
ENV TZ=Asia/Shanghai
|
||||||
@@ -74,15 +74,20 @@ RUN --mount=type=cache,target=/go/pkg/mod \
|
|||||||
FROM alpine:3.20
|
FROM alpine:3.20
|
||||||
|
|
||||||
# 使用国内 Alpine 镜像源加速 // Logan.Li
|
# 使用国内 Alpine 镜像源加速 // Logan.Li
|
||||||
RUN sed -i 's|dl-cdn.alpinelinux.org|mirrors.aliyun.com|g' /etc/apk/repositories
|
RUN sed -i 's|dl-cdn.alpinelinux.org|mirrors.tuna.tsinghua.edu.cn|g' /etc/apk/repositories
|
||||||
|
|
||||||
# 安装运行时依赖(核心最小集,重型工具改为懒加载)// Logan.Li
|
# 安装运行时依赖(核心最小集,重型工具改为懒加载)// Logan.Li
|
||||||
# bash=kubectl交互终端, curl=健康检查, git=流水线代码拉取, docker-cli=镜像构建推送
|
# bash=kubectl交互终端, curl=健康检查, git=流水线代码拉取, docker-cli=镜像构建推送
|
||||||
# kubectl/maven/nodejs/helm → 见「发布配置→工具配置」Tab,首次使用自动下载
|
# kubectl=预装(~50MB,K8s交互终端必需),maven/nodejs/helm → 工具配置Tab懒加载
|
||||||
# Pod文件浏览/exec 通过 WebSocket channel 协议直连 K8s API,无需 kubectl
|
# Pod文件浏览/exec 通过 WebSocket channel 协议直连 K8s API,无需 kubectl
|
||||||
RUN --mount=type=cache,target=/var/cache/apk \
|
RUN --mount=type=cache,target=/var/cache/apk \
|
||||||
apk add ca-certificates tzdata bash curl git docker-cli docker-cli-buildx
|
apk add ca-certificates tzdata bash curl git docker-cli docker-cli-buildx
|
||||||
|
|
||||||
|
# kubectl 预装(~50MB,国内网络从 dl.k8s.io 下载约10s)// Logan.Li
|
||||||
|
ARG KUBECTL_VERSION=v1.32.0
|
||||||
|
RUN curl -sLo /usr/local/bin/kubectl https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl && \
|
||||||
|
chmod +x /usr/local/bin/kubectl
|
||||||
|
|
||||||
# npm 淘宝镜像加速(node 懒加载后生效)
|
# npm 淘宝镜像加速(node 懒加载后生效)
|
||||||
RUN npm config set registry https://registry.npmmirror.com 2>/dev/null || true
|
RUN npm config set registry https://registry.npmmirror.com 2>/dev/null || true
|
||||||
# Maven 阿里云镜像加速(maven 懒加载后生效)
|
# Maven 阿里云镜像加速(maven 懒加载后生效)
|
||||||
|
|||||||
@@ -269,6 +269,10 @@ func cleanResourceYAML(obj map[string]interface{}) {
|
|||||||
delete(meta, "annotations")
|
delete(meta, "annotations")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 移除空的 labels
|
||||||
|
if labels, ok := meta["labels"].(map[string]interface{}); ok && len(labels) == 0 {
|
||||||
|
delete(meta, "labels")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 删除 status
|
// 删除 status
|
||||||
@@ -276,11 +280,21 @@ func cleanResourceYAML(obj map[string]interface{}) {
|
|||||||
|
|
||||||
// 清理 spec 中的运行时字段
|
// 清理 spec 中的运行时字段
|
||||||
if spec, ok := obj["spec"].(map[string]interface{}); ok {
|
if spec, ok := obj["spec"].(map[string]interface{}); ok {
|
||||||
// Service: 移除动态分配的 clusterIP 和 nodePorts
|
kind, _ := obj["kind"].(string)
|
||||||
if obj["kind"] == "Service" {
|
|
||||||
|
// Service: 移除动态分配字段和默认值
|
||||||
|
if kind == "Service" {
|
||||||
delete(spec, "clusterIP")
|
delete(spec, "clusterIP")
|
||||||
delete(spec, "clusterIPs")
|
delete(spec, "clusterIPs")
|
||||||
// nodePorts 在 ports 数组内 — 仅移除 NodePort 类型的 nodePort 字段
|
delete(spec, "internalTrafficPolicy")
|
||||||
|
delete(spec, "ipFamilies")
|
||||||
|
delete(spec, "ipFamilyPolicy")
|
||||||
|
// sessionAffinity 默认 "None" → 移除
|
||||||
|
if sa, ok := spec["sessionAffinity"].(string); ok && sa == "None" {
|
||||||
|
delete(spec, "sessionAffinity")
|
||||||
|
}
|
||||||
|
delete(spec, "allocateLoadBalancerNodePorts")
|
||||||
|
// 移除 ports 内的 nodePort(仅 NodePort/LoadBalancer 类型)
|
||||||
if ports, ok := spec["ports"].([]interface{}); ok {
|
if ports, ok := spec["ports"].([]interface{}); ok {
|
||||||
for _, p := range ports {
|
for _, p := range ports {
|
||||||
if pm, ok := p.(map[string]interface{}); ok {
|
if pm, ok := p.(map[string]interface{}); ok {
|
||||||
@@ -290,8 +304,9 @@ func cleanResourceYAML(obj map[string]interface{}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deployment: 清理 template.metadata 和 template.spec 中的运行时字段
|
// Deployment / StatefulSet / DaemonSet: 清理运行时和默认值
|
||||||
if obj["kind"] == "Deployment" {
|
if kind == "Deployment" || kind == "StatefulSet" || kind == "DaemonSet" {
|
||||||
|
// 清理 template
|
||||||
if template, ok := spec["template"].(map[string]interface{}); ok {
|
if template, ok := spec["template"].(map[string]interface{}); ok {
|
||||||
if tMeta, ok := template["metadata"].(map[string]interface{}); ok {
|
if tMeta, ok := template["metadata"].(map[string]interface{}); ok {
|
||||||
delete(tMeta, "creationTimestamp")
|
delete(tMeta, "creationTimestamp")
|
||||||
@@ -304,8 +319,54 @@ func cleanResourceYAML(obj map[string]interface{}) {
|
|||||||
}
|
}
|
||||||
if tSpec, ok := template["spec"].(map[string]interface{}); ok {
|
if tSpec, ok := template["spec"].(map[string]interface{}); ok {
|
||||||
delete(tSpec, "nodeName")
|
delete(tSpec, "nodeName")
|
||||||
|
delete(tSpec, "serviceAccountName") // 移除 default 值
|
||||||
|
// 清理容器内的 terminationMessagePath/Policy 默认值
|
||||||
|
if containers, ok := tSpec["containers"].([]interface{}); ok {
|
||||||
|
for _, c := range containers {
|
||||||
|
if cm, ok := c.(map[string]interface{}); ok {
|
||||||
|
if tmp, ok := cm["terminationMessagePath"].(string); ok && tmp == "/dev/termination-log" {
|
||||||
|
delete(cm, "terminationMessagePath")
|
||||||
|
}
|
||||||
|
if tmp, ok := cm["terminationMessagePolicy"].(string); ok && tmp == "File" {
|
||||||
|
delete(cm, "terminationMessagePolicy")
|
||||||
|
}
|
||||||
|
// 清理默认 imagePullPolicy
|
||||||
|
if ip, ok := cm["imagePullPolicy"].(string); ok && ip == "Always" {
|
||||||
|
delete(cm, "imagePullPolicy")
|
||||||
|
}
|
||||||
|
// 清理空 resources
|
||||||
|
if res, ok := cm["resources"].(map[string]interface{}); ok && len(res) == 0 {
|
||||||
|
delete(cm, "resources")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 清理 dnsPolicy 默认值
|
||||||
|
if dp, ok := tSpec["dnsPolicy"].(string); ok && dp == "ClusterFirst" {
|
||||||
|
delete(tSpec, "dnsPolicy")
|
||||||
|
}
|
||||||
|
if rp, ok := tSpec["restartPolicy"].(string); ok && rp == "Always" {
|
||||||
|
delete(tSpec, "restartPolicy")
|
||||||
|
}
|
||||||
|
if tp, ok := tSpec["terminationGracePeriodSeconds"].(float64); ok && tp == 30 {
|
||||||
|
delete(tSpec, "terminationGracePeriodSeconds")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 清理默认值
|
||||||
|
if rhl, ok := spec["revisionHistoryLimit"].(float64); ok && rhl == 10 {
|
||||||
|
delete(spec, "revisionHistoryLimit")
|
||||||
|
}
|
||||||
|
if pds, ok := spec["progressDeadlineSeconds"].(float64); ok && pds == 600 {
|
||||||
|
delete(spec, "progressDeadlineSeconds")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ingress: 清理默认值
|
||||||
|
if kind == "Ingress" {
|
||||||
|
if ic, ok := spec["ingressClassName"].(string); ok && ic == "" {
|
||||||
|
delete(spec, "ingressClassName")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1605,13 +1605,14 @@ func (h *KubernetesHandler) listDeploymentsWithNS(c *gin.Context, ns string) {
|
|||||||
deps, err := client.ListDeployments()
|
deps, err := client.ListDeployments()
|
||||||
if err != nil { c.JSON(500, models.Response{Code: 500, Message: "K8s API error: " + err.Error()}); return }
|
if err != nil { c.JSON(500, models.Response{Code: 500, Message: "K8s API error: " + err.Error()}); return }
|
||||||
type DepSummary struct {
|
type DepSummary struct {
|
||||||
Namespace string `json:"namespace"`
|
Namespace string `json:"namespace"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Replicas int `json:"replicas"`
|
Replicas int `json:"replicas"`
|
||||||
AvailableReplicas int `json:"available_replicas"`
|
AvailableReplicas int `json:"available_replicas"`
|
||||||
ReadyReplicas int `json:"ready_replicas"`
|
ReadyReplicas int `json:"ready_replicas"`
|
||||||
Image string `json:"image"`
|
Image string `json:"image"`
|
||||||
Paused bool `json:"paused"`
|
Paused bool `json:"paused"`
|
||||||
|
Selector map[string]string `json:"selector,omitempty"` // matchLabels — 前端 Service Tab 关联用
|
||||||
}
|
}
|
||||||
result := []DepSummary{}
|
result := []DepSummary{}
|
||||||
for _, d := range deps.Items {
|
for _, d := range deps.Items {
|
||||||
@@ -1625,6 +1626,7 @@ func (h *KubernetesHandler) listDeploymentsWithNS(c *gin.Context, ns string) {
|
|||||||
ReadyReplicas: d.Status.ReadyReplicas,
|
ReadyReplicas: d.Status.ReadyReplicas,
|
||||||
Image: image,
|
Image: image,
|
||||||
Paused: d.Spec.Paused,
|
Paused: d.Spec.Paused,
|
||||||
|
Selector: func() map[string]string { if d.Spec.Selector != nil { return d.Spec.Selector.MatchLabels }; return nil }(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
c.JSON(200, models.Response{Code: 200, Data: result})
|
c.JSON(200, models.Response{Code: 200, Data: result})
|
||||||
|
|||||||
@@ -66,9 +66,9 @@ func PredefinedTools() []DeployTool {
|
|||||||
{
|
{
|
||||||
Name: "kubectl",
|
Name: "kubectl",
|
||||||
DisplayName: "Kubectl",
|
DisplayName: "Kubectl",
|
||||||
Description: "Kubernetes 命令行工具,用于 K8s 集群交互",
|
Description: "Kubernetes 命令行工具,用于 K8s 集群交互(已预装在镜像中)",
|
||||||
Category: "k8s",
|
Category: "k8s",
|
||||||
InstallMethod: ToolLazy,
|
InstallMethod: ToolPreinstall,
|
||||||
BinaryName: "kubectl",
|
BinaryName: "kubectl",
|
||||||
InstallPath: "/usr/local/bin/kubectl",
|
InstallPath: "/usr/local/bin/kubectl",
|
||||||
CheckCommand: "kubectl version --client --short 2>/dev/null || kubectl version --client 2>/dev/null",
|
CheckCommand: "kubectl version --client --short 2>/dev/null || kubectl version --client 2>/dev/null",
|
||||||
|
|||||||
@@ -290,10 +290,16 @@ type DeploymentItem struct {
|
|||||||
|
|
||||||
type DeploymentSpec struct {
|
type DeploymentSpec struct {
|
||||||
Replicas int `json:"replicas"`
|
Replicas int `json:"replicas"`
|
||||||
|
Selector *LabelSelector `json:"selector,omitempty"` // matchLabels — 前端 Service 关联用
|
||||||
Template DeploymentTemplate `json:"template"`
|
Template DeploymentTemplate `json:"template"`
|
||||||
Paused bool `json:"paused"`
|
Paused bool `json:"paused"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LabelSelector K8s 标签选择器
|
||||||
|
type LabelSelector struct {
|
||||||
|
MatchLabels map[string]string `json:"matchLabels,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type DeploymentTemplate struct {
|
type DeploymentTemplate struct {
|
||||||
Spec PodSpec `json:"spec"`
|
Spec PodSpec `json:"spec"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ function App() {
|
|||||||
<Route path="pipelines/:code" element={<PipelineEditor />} />
|
<Route path="pipelines/:code" element={<PipelineEditor />} />
|
||||||
<Route path="pipelines/:code/runs" element={<PipelineRunDetail />} />
|
<Route path="pipelines/:code/runs" element={<PipelineRunDetail />} />
|
||||||
<Route path="pipelines/:code/edit" element={<PipelineEditor />} />
|
<Route path="pipelines/:code/edit" element={<PipelineEditor />} />
|
||||||
<Route path="deploy/credentials" element={<DeployCredentials />} />
|
<Route path="deploy/configureTools" element={<DeployCredentials />} />
|
||||||
<Route path="deploy/groups" element={<UserGroupsManager />} />
|
<Route path="deploy/groups" element={<UserGroupsManager />} />
|
||||||
<Route path="deploy/rbac" element={<DeployRBAC />} />
|
<Route path="deploy/rbac" element={<DeployRBAC />} />
|
||||||
{/* 监控告警 */}
|
{/* 监控告警 */}
|
||||||
|
|||||||
@@ -67,8 +67,10 @@ export function DeploymentEditService({ clusterCode, namespace, deploymentName,
|
|||||||
const load = useCallback(async () => {
|
const load = useCallback(async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
// 先从 manifest 取 selector
|
// 先从 manifest 取 selector(兼容两种来源)
|
||||||
const sel = manifest?.spec?.selector?.matchLabels || {};
|
// full K8s manifest: spec.selector.matchLabels
|
||||||
|
// DepSummary 降级: selector (顶层)
|
||||||
|
const sel = manifest?.spec?.selector?.matchLabels || manifest?.selector || {};
|
||||||
setSelectorLabels(sel);
|
setSelectorLabels(sel);
|
||||||
|
|
||||||
const [svcRes, ingRes] = await Promise.all([
|
const [svcRes, ingRes] = await Promise.all([
|
||||||
@@ -275,7 +277,7 @@ export function DeploymentEditService({ clusterCode, namespace, deploymentName,
|
|||||||
<CardTitle className="text-sm text-slate-200 flex items-center gap-2">
|
<CardTitle className="text-sm text-slate-200 flex items-center gap-2">
|
||||||
<Network className="h-4 w-4 text-blue-400" />
|
<Network className="h-4 w-4 text-blue-400" />
|
||||||
Services
|
Services
|
||||||
<Badge variant="outline" className="border-slate-700 text-slate-400">{matchedSvcs.length}/{services.length}</Badge>
|
<Badge variant="outline" className="border-slate-700 text-slate-400">{matchedSvcs.length} 个关联</Badge>
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<Button size="sm" variant="outline" className="h-7 border-slate-700 text-slate-300 text-xs"
|
<Button size="sm" variant="outline" className="h-7 border-slate-700 text-slate-300 text-xs"
|
||||||
onClick={() => { setSvcForm(s => ({ ...s, name: `${deploymentName}-svc`, port: '80', targetPort: '80' })); setShowCreate('service'); }}>
|
onClick={() => { setSvcForm(s => ({ ...s, name: `${deploymentName}-svc`, port: '80', targetPort: '80' })); setShowCreate('service'); }}>
|
||||||
@@ -283,18 +285,18 @@ export function DeploymentEditService({ clusterCode, namespace, deploymentName,
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-slate-500 mt-1">
|
<p className="text-xs text-slate-500 mt-1">
|
||||||
高亮的是 selector 匹配此 Deployment 的 Service({matchedSvcs.length} 个关联 / {services.length} 个总数)
|
仅显示 selector 匹配此 Deployment 的关联 Service
|
||||||
</p>
|
</p>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="pt-0">
|
<CardContent className="pt-0">
|
||||||
{services.length === 0 ? (
|
{matchedSvcs.length === 0 ? (
|
||||||
<div className="text-center py-6 text-slate-500 text-sm border border-dashed border-slate-800 rounded-lg">
|
<div className="text-center py-6 text-slate-500 text-sm border border-dashed border-slate-800 rounded-lg">
|
||||||
<Network className="h-8 w-8 mx-auto mb-2 text-slate-700" />
|
<Network className="h-8 w-8 mx-auto mb-2 text-slate-700" />
|
||||||
当前命名空间暂无 Service
|
当前命名空间暂无 Service
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{services.map(svc => {
|
{matchedSvcs.map(svc => {
|
||||||
const matched = selectorMatches(svc.selector);
|
const matched = selectorMatches(svc.selector);
|
||||||
return (
|
return (
|
||||||
<div key={svc.name} className={`rounded-lg border p-3 ${matched ? 'border-blue-700/50 bg-blue-950/20' : 'border-slate-800 bg-slate-950/30'}`}>
|
<div key={svc.name} className={`rounded-lg border p-3 ${matched ? 'border-blue-700/50 bg-blue-950/20' : 'border-slate-800 bg-slate-950/30'}`}>
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ const menuItems: MenuItem[] = [
|
|||||||
id: 'deploy', name: '应用发布', icon: Rocket,
|
id: 'deploy', name: '应用发布', icon: Rocket,
|
||||||
children: [
|
children: [
|
||||||
{ id: 'pipelines', name: '流水线', icon: GitBranch, path: '/pipelines' },
|
{ id: 'pipelines', name: '流水线', icon: GitBranch, path: '/pipelines' },
|
||||||
{ id: 'deploy-credentials', name: '发布配置', icon: Key, path: '/deploy/credentials' },
|
{ id: 'deploy-credentials', name: '发布配置', icon: Key, path: '/deploy/configureTools' },
|
||||||
{ id: 'deploy-rbac', name: '发布权限', icon: Shield, path: '/deploy/rbac' },
|
{ id: 'deploy-rbac', name: '发布权限', icon: Shield, path: '/deploy/rbac' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in new issue
Block a user