fix: agent install.sh 404 + 三阶段构建编译agent二进制
问题: 构建上下文 ./backend 无法访问 ../agent, 导致容器内
/app/agent/deploy/install.sh 不存在, 返回 404
修复:
- Dockerfile 改为三阶段构建 (backend go1.21 + agent go1.24 交叉编译 + alpine runtime)
- docker-compose backend build context 改为项目根
- routes.go /agent/arch 安装命令改为动态获取 URL
- 新增 .dockerignore
- agent_install.go: SSH 远程安装 handler
- AgentWizard.tsx: 复制按钮 fallback + 动态 server URL
- ServerTableRow.tsx: agent 状态判断
验证: curl|bash 安装成功, agent 上报数据到 MySQL ✅
This commit is contained in:
1 parent
d0344c2f52
commit
d0b94d39c0
9 files changed
+281
-38
No files matched your search
@@ -0,0 +1,29 @@
|
|||||||
|
# Git
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
|
||||||
|
# 前端构建产物和依赖(后端构建不需要)
|
||||||
|
frontend/node_modules
|
||||||
|
frontend/dist
|
||||||
|
frontend/.cache
|
||||||
|
|
||||||
|
# 日志
|
||||||
|
**/*.log
|
||||||
|
backend/logs/
|
||||||
|
|
||||||
|
# 敏感配置
|
||||||
|
backend/config.yaml
|
||||||
|
**/.env
|
||||||
|
|
||||||
|
# Docker 自身
|
||||||
|
docker-compose.yml
|
||||||
|
Dockerfile
|
||||||
|
**/Dockerfile
|
||||||
|
|
||||||
|
# 文档
|
||||||
|
*.md
|
||||||
|
docs/
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
+51
-29
@@ -1,60 +1,82 @@
|
|||||||
# 构建阶段
|
# ============================================================
|
||||||
|
# AIOps Backend + Agent 多阶段 Dockerfile
|
||||||
|
# Stage 1: 编译后端 (Go 1.21)
|
||||||
|
# Stage 2: 交叉编译 Agent (Go 1.24, linux-amd64/arm64 + darwin-arm64)
|
||||||
|
# Stage 3: 运行时 (Alpine)
|
||||||
|
# 署名 Logan.Li
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
# ==================== Stage 1: Backend Builder ====================
|
||||||
FROM golang:1.21-alpine AS builder
|
FROM golang:1.21-alpine AS builder
|
||||||
|
|
||||||
# 设置工作目录
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# 安装构建依赖
|
|
||||||
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
|
||||||
|
|
||||||
# 设置 Go 模块代理(国内加速)
|
|
||||||
ENV GOPROXY=https://goproxy.cn,direct
|
ENV GOPROXY=https://goproxy.cn,direct
|
||||||
ENV GOSUMDB=off
|
ENV GOSUMDB=off
|
||||||
|
|
||||||
# 复制 go.mod 和 go.sum
|
# 复制后端 go.mod(利用 Docker 层缓存)
|
||||||
COPY go.mod go.sum ./
|
COPY backend/go.mod backend/go.sum ./
|
||||||
|
|
||||||
# 下载依赖
|
|
||||||
RUN go mod download
|
RUN go mod download
|
||||||
|
|
||||||
# 复制源代码
|
# 复制后端源码
|
||||||
COPY . .
|
COPY backend/ ./
|
||||||
|
|
||||||
# 构建应用
|
# 构建后端二进制
|
||||||
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
|
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
|
||||||
-ldflags="-w -s \
|
-ldflags="-w -s" \
|
||||||
-X main.Version=$(git describe --tags --always --dirty 2>/dev/null || echo 'dev') \
|
|
||||||
-X main.BuildTime=$(date +%Y-%m-%d_%H:%M:%S) \
|
|
||||||
-X main.GitCommit=$(git rev-parse --short HEAD 2>/dev/null || echo 'unknown')" \
|
|
||||||
-o ezops ./cmd/
|
-o ezops ./cmd/
|
||||||
|
|
||||||
# 运行阶段
|
# ==================== Stage 2: Agent Builder ====================
|
||||||
|
FROM golang:1.24-alpine AS agent-builder
|
||||||
|
|
||||||
|
WORKDIR /agent
|
||||||
|
|
||||||
|
ENV GOPROXY=https://goproxy.cn,direct
|
||||||
|
ENV GOSUMDB=off
|
||||||
|
|
||||||
|
# 复制 agent go.mod
|
||||||
|
COPY agent/go.mod agent/go.sum ./
|
||||||
|
RUN go mod download
|
||||||
|
|
||||||
|
# 复制 agent 源码
|
||||||
|
COPY agent/ ./
|
||||||
|
|
||||||
|
# 交叉编译三个平台的 agent 二进制
|
||||||
|
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o bin/aiops-agent-linux-amd64 . && \
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="-s -w" -o bin/aiops-agent-linux-arm64 . && \
|
||||||
|
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -ldflags="-s -w" -o bin/aiops-agent-darwin-arm64 .
|
||||||
|
|
||||||
|
# ==================== Stage 3: Runtime ====================
|
||||||
FROM alpine:latest
|
FROM alpine:latest
|
||||||
|
|
||||||
# 安装运行时依赖
|
# 安装运行时依赖(curl=健康检查,openssh-client=SSH远程安装agent)
|
||||||
RUN apk --no-cache add ca-certificates tzdata curl
|
RUN apk --no-cache add ca-certificates tzdata curl openssh-client
|
||||||
|
|
||||||
# 设置时区
|
|
||||||
ENV TZ=Asia/Shanghai
|
ENV TZ=Asia/Shanghai
|
||||||
|
|
||||||
# 设置工作目录
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# 复制构建产物
|
# 复制后端二进制
|
||||||
COPY --from=builder /app/ezops .
|
COPY --from=builder /app/ezops .
|
||||||
|
|
||||||
# 创建日志目录
|
# 创建运行时目录
|
||||||
RUN mkdir -p /app/logs /app/uploads
|
RUN mkdir -p /app/logs /app/uploads /app/agent/bin /app/agent/deploy
|
||||||
|
|
||||||
|
# 复制 agent 二进制(三个平台)
|
||||||
|
COPY --from=agent-builder /agent/bin/aiops-agent-linux-amd64 /app/agent/bin/
|
||||||
|
COPY --from=agent-builder /agent/bin/aiops-agent-linux-arm64 /app/agent/bin/
|
||||||
|
COPY --from=agent-builder /agent/bin/aiops-agent-darwin-arm64 /app/agent/bin/
|
||||||
|
|
||||||
|
# 复制 agent 安装脚本和 systemd service 模板
|
||||||
|
COPY agent/deploy/install.sh /app/agent/deploy/
|
||||||
|
COPY agent/deploy/aiops-agent.service /app/agent/deploy/
|
||||||
|
|
||||||
# 暴露端口
|
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
|
||||||
# 健康检查
|
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
|
||||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
|
||||||
CMD curl -f http://localhost:8080/health || exit 1
|
CMD curl -f http://localhost:8080/health || exit 1
|
||||||
|
|
||||||
# 启动应用
|
|
||||||
CMD ["./ezops"]
|
CMD ["./ezops"]
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"ezops/internal/models"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AgentInstallHandler Agent 远程安装处理器 // Logan.Li
|
||||||
|
type AgentInstallHandler struct {
|
||||||
|
db *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAgentInstallHandler(db *gorm.DB) *AgentInstallHandler {
|
||||||
|
return &AgentInstallHandler{db: db}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sshDial 复用 webshell.go 的 SSH 连接逻辑
|
||||||
|
func sshDial(server models.Server) (*ssh.Client, error) {
|
||||||
|
config := &ssh.ClientConfig{
|
||||||
|
User: server.Username,
|
||||||
|
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||||
|
Timeout: 10 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
switch server.AuthType {
|
||||||
|
case "key":
|
||||||
|
signer, err := ssh.ParsePrivateKey([]byte(server.SSHKey))
|
||||||
|
if err != nil {
|
||||||
|
config.Auth = []ssh.AuthMethod{ssh.Password(server.Password)}
|
||||||
|
} else {
|
||||||
|
config.Auth = []ssh.AuthMethod{ssh.PublicKeys(signer)}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
config.Auth = []ssh.AuthMethod{ssh.Password(server.Password)}
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := server.IP + ":" + strconv.Itoa(server.Port)
|
||||||
|
return ssh.Dial("tcp", addr, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
// runSSHCommand 在远程主机执行命令并返回输出
|
||||||
|
func runSSHCommand(client *ssh.Client, cmd string) (string, error) {
|
||||||
|
session, err := client.NewSession()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer session.Close()
|
||||||
|
|
||||||
|
output, err := session.CombinedOutput(cmd)
|
||||||
|
return string(output), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// InstallAgent 远程安装 Agent 到指定服务器
|
||||||
|
// POST /servers/:id/install-agent
|
||||||
|
func (h *AgentInstallHandler) InstallAgent(c *gin.Context) {
|
||||||
|
id, _ := strconv.Atoi(c.Param("id"))
|
||||||
|
|
||||||
|
var server models.Server
|
||||||
|
if err := h.db.First(&server, id).Error; err != nil {
|
||||||
|
c.JSON(http.StatusNotFound, models.Response{Code: 404, Message: "服务器不存在"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if server.Status != "running" {
|
||||||
|
c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: "服务器未运行,无法安装 Agent"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. SSH 连接目标主机
|
||||||
|
client, err := sshDial(server)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[AgentInstall] SSH连接失败 %s: %v", server.IP, err)
|
||||||
|
c.JSON(http.StatusOK, models.Response{
|
||||||
|
Code: 500,
|
||||||
|
Message: "SSH 连接失败: " + formatSSHError(err),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer client.Close()
|
||||||
|
|
||||||
|
// 2. 检测系统架构
|
||||||
|
archOut, _ := runSSHCommand(client, "uname -m")
|
||||||
|
arch := "amd64"
|
||||||
|
if len(archOut) > 0 && (contains(archOut, "aarch64") || contains(archOut, "arm64")) {
|
||||||
|
arch = "arm64"
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 构造安装命令(使用 Agent API 服务地址)
|
||||||
|
// 从请求头获取后端可访问地址
|
||||||
|
serverURL := fmt.Sprintf("http://%s", c.Request.Host)
|
||||||
|
token := c.Query("token")
|
||||||
|
|
||||||
|
installCmd := fmt.Sprintf(
|
||||||
|
`curl -fsSL %s/agent/install.sh | sudo bash -s -- --server %s --token %s`,
|
||||||
|
serverURL, serverURL, token,
|
||||||
|
)
|
||||||
|
|
||||||
|
// 4. 执行安装
|
||||||
|
installOut, err := runSSHCommand(client, installCmd)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusOK, models.Response{
|
||||||
|
Code: 500,
|
||||||
|
Message: "安装命令执行失败(可能需要 sudo 权限或 curl 未安装)",
|
||||||
|
Data: installOut,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 验证安装是否成功
|
||||||
|
verifyOut, _ := runSSHCommand(client, "systemctl is-active aiops-agent 2>/dev/null || echo 'not_found'")
|
||||||
|
installed := contains(verifyOut, "active") || contains(verifyOut, "running")
|
||||||
|
|
||||||
|
if installed {
|
||||||
|
// 更新服务器 Agent 状态
|
||||||
|
h.db.Model(&server).Updates(map[string]interface{}{
|
||||||
|
"agent_status": "online",
|
||||||
|
"agent_version": "installed",
|
||||||
|
"last_check_at": time.Now(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, models.Response{
|
||||||
|
Code: 200,
|
||||||
|
Message: func() string {
|
||||||
|
if installed {
|
||||||
|
return "Agent 安装成功,已自动启动并设置开机自启"
|
||||||
|
}
|
||||||
|
return "安装命令已执行,但 Agent 服务未检测到运行(请查看日志排查)"
|
||||||
|
}(),
|
||||||
|
Data: gin.H{
|
||||||
|
"installed": installed,
|
||||||
|
"arch": arch,
|
||||||
|
"output": installOut,
|
||||||
|
"verify": verifyOut,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"ezops/internal/api/handlers"
|
"ezops/internal/api/handlers"
|
||||||
"ezops/internal/api/middleware"
|
"ezops/internal/api/middleware"
|
||||||
"ezops/internal/config"
|
"ezops/internal/config"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
@@ -28,6 +29,19 @@ func Register(r *gin.Engine, db *gorm.DB, log *zap.Logger, cfg *config.Config) {
|
|||||||
r.StaticFile("/agent/aiops-agent-linux-arm64", "./agent/bin/aiops-agent-linux-arm64")
|
r.StaticFile("/agent/aiops-agent-linux-arm64", "./agent/bin/aiops-agent-linux-arm64")
|
||||||
r.StaticFile("/agent/aiops-agent-darwin-arm64", "./agent/bin/aiops-agent-darwin-arm64") // macOS Apple Silicon // Logan.Li
|
r.StaticFile("/agent/aiops-agent-darwin-arm64", "./agent/bin/aiops-agent-darwin-arm64") // macOS Apple Silicon // Logan.Li
|
||||||
r.GET("/agent/arch", func(c *gin.Context) {
|
r.GET("/agent/arch", func(c *gin.Context) {
|
||||||
|
// 动态构建安装命令 URL(根据请求来源,兼容 nginx 反代)
|
||||||
|
scheme := "http"
|
||||||
|
if c.Request.TLS != nil {
|
||||||
|
scheme = "https"
|
||||||
|
}
|
||||||
|
if proto := c.GetHeader("X-Forwarded-Proto"); proto != "" {
|
||||||
|
scheme = proto
|
||||||
|
}
|
||||||
|
host := c.Request.Host
|
||||||
|
if host == "" {
|
||||||
|
host = "localhost:8080"
|
||||||
|
}
|
||||||
|
base := fmt.Sprintf("%s://%s", scheme, host)
|
||||||
c.JSON(200, gin.H{
|
c.JSON(200, gin.H{
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"downloads": gin.H{
|
"downloads": gin.H{
|
||||||
@@ -35,7 +49,7 @@ func Register(r *gin.Engine, db *gorm.DB, log *zap.Logger, cfg *config.Config) {
|
|||||||
"linux-arm64": "/agent/aiops-agent-linux-arm64",
|
"linux-arm64": "/agent/aiops-agent-linux-arm64",
|
||||||
"darwin-arm64": "/agent/aiops-agent-darwin-arm64",
|
"darwin-arm64": "/agent/aiops-agent-darwin-arm64",
|
||||||
},
|
},
|
||||||
"install": "curl -fsSL http://<server>:8080/agent/install.sh | sudo bash -s -- --server http://<server>:8080 --token <token>",
|
"install": fmt.Sprintf("curl -fsSL %s/agent/install.sh | sudo bash -s -- --server %s --token <token>", base, base),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -122,6 +136,10 @@ func Register(r *gin.Engine, db *gorm.DB, log *zap.Logger, cfg *config.Config) {
|
|||||||
servers.DELETE("/:id", serverHandler.DeleteServer)
|
servers.DELETE("/:id", serverHandler.DeleteServer)
|
||||||
servers.POST("/batch-delete", serverHandler.BatchDeleteServers)
|
servers.POST("/batch-delete", serverHandler.BatchDeleteServers)
|
||||||
servers.POST("/batch", serverHandler.BatchCreateServers)
|
servers.POST("/batch", serverHandler.BatchCreateServers)
|
||||||
|
|
||||||
|
// Agent 远程安装 // Logan.Li
|
||||||
|
agentInstallHandler := handlers.NewAgentInstallHandler(db)
|
||||||
|
servers.POST("/:id/install-agent", agentInstallHandler.InstallAgent)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 云账号路由
|
// 云账号路由
|
||||||
|
|||||||
+2
-2
@@ -50,8 +50,8 @@ services:
|
|||||||
# ======================== Go 后端服务 ========================
|
# ======================== Go 后端服务 ========================
|
||||||
backend:
|
backend:
|
||||||
build:
|
build:
|
||||||
context: ./backend
|
context: .
|
||||||
dockerfile: Dockerfile
|
dockerfile: backend/Dockerfile
|
||||||
container_name: aiops-backend
|
container_name: aiops-backend
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
|
|||||||
@@ -48,6 +48,14 @@ server {
|
|||||||
proxy_read_timeout 86400;
|
proxy_read_timeout 86400;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Agent 安装脚本和二进制文件代理 → 后端
|
||||||
|
location /agent/ {
|
||||||
|
proxy_pass http://backend:8080;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
}
|
||||||
|
|
||||||
# 健康检查代理
|
# 健康检查代理
|
||||||
location /health {
|
location /health {
|
||||||
proxy_pass http://backend:8080;
|
proxy_pass http://backend:8080;
|
||||||
|
|||||||
@@ -17,11 +17,12 @@ interface AgentWizardProps {
|
|||||||
onOpenChange: (open: boolean) => void;
|
onOpenChange: (open: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 自动检测服务器地址:使用当前浏览器访问的主机名,端口固定 8080 */
|
/** 自动检测服务器地址:使用当前浏览器访问的地址(协议+主机+端口) */
|
||||||
function detectServerAddress(): string {
|
function detectServerAddress(): string {
|
||||||
if (typeof window !== 'undefined' && window.location) {
|
if (typeof window !== 'undefined' && window.location) {
|
||||||
const host = window.location.hostname;
|
// 直接用当前浏览器的 origin(如 http://10.10.10.150),
|
||||||
return host ? `http://${host}:8080` : 'http://127.0.0.1:8080';
|
// Nginx 会将 /agent/ 请求代理到后端
|
||||||
|
return window.location.origin;
|
||||||
}
|
}
|
||||||
return 'http://127.0.0.1:8080';
|
return 'http://127.0.0.1:8080';
|
||||||
}
|
}
|
||||||
@@ -35,12 +36,27 @@ export function AgentWizard({ open, onOpenChange }: AgentWizardProps) {
|
|||||||
|
|
||||||
const handleCopy = async () => {
|
const handleCopy = async () => {
|
||||||
try {
|
try {
|
||||||
await navigator.clipboard.writeText(installCommand);
|
// 优先使用现代 Clipboard API(需要 HTTPS 或 localhost)
|
||||||
|
if (navigator.clipboard && window.isSecureContext) {
|
||||||
|
await navigator.clipboard.writeText(installCommand);
|
||||||
|
} else {
|
||||||
|
// 降级方案:用隐藏 textarea + execCommand('copy'),兼容 HTTP 环境
|
||||||
|
const textarea = document.createElement('textarea');
|
||||||
|
textarea.value = installCommand;
|
||||||
|
textarea.style.position = 'fixed';
|
||||||
|
textarea.style.opacity = '0';
|
||||||
|
textarea.style.pointerEvents = 'none';
|
||||||
|
document.body.appendChild(textarea);
|
||||||
|
textarea.select();
|
||||||
|
const ok = document.execCommand('copy');
|
||||||
|
document.body.removeChild(textarea);
|
||||||
|
if (!ok) throw new Error('execCommand failed');
|
||||||
|
}
|
||||||
setCopied(true);
|
setCopied(true);
|
||||||
toast.success('安装命令已复制');
|
toast.success('安装命令已复制');
|
||||||
setTimeout(() => setCopied(false), 2000);
|
setTimeout(() => setCopied(false), 2000);
|
||||||
} catch {
|
} catch {
|
||||||
toast.error('复制失败,请手动复制');
|
toast.error('复制失败,请手动选中复制');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from '@/components/ui/dropdown-menu';
|
} from '@/components/ui/dropdown-menu';
|
||||||
import { TableCell, TableRow } from '@/components/ui/table';
|
import { TableCell, TableRow } from '@/components/ui/table';
|
||||||
import { Edit, MoreHorizontal, Server as ServerIcon, Terminal, Trash2 } from 'lucide-react';
|
import { Edit, MoreHorizontal, Server as ServerIcon, Terminal, Trash2, Download } from 'lucide-react';
|
||||||
import { getStatusBadge, getTags, getUsageColor } from './serverHelpers';
|
import { getStatusBadge, getTags, getUsageColor } from './serverHelpers';
|
||||||
|
|
||||||
interface ServerTableRowProps {
|
interface ServerTableRowProps {
|
||||||
|
|||||||
@@ -143,6 +143,10 @@ export const serverApi = {
|
|||||||
// 后端暂无单机 metrics,用 dashboard metrics
|
// 后端暂无单机 metrics,用 dashboard metrics
|
||||||
return http.get('/dashboard/metrics');
|
return http.get('/dashboard/metrics');
|
||||||
},
|
},
|
||||||
|
// 远程安装 Agent(通过 SSH 执行安装脚本)
|
||||||
|
installAgent: async (id: number | string, token: string) => {
|
||||||
|
return http.post<{ installed: boolean; arch: string; output: string }>(`/servers/${id}/install-agent?token=${encodeURIComponent(token)}`);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// ==================== K8s 集群 API ====================
|
// ==================== K8s 集群 API ====================
|
||||||
|
|||||||
Reference in new issue
Block a user