fix: AgentWizard URL拼接修正(origin/backend分离) + 复制兼容HTTP/移动端

This commit is contained in:
attacker committed 2026-06-18 07:14:19 +00:00
1 parent 2f4c0df0f2
commit 48b2d2281b
7 files changed
+60 -46

No files matched your search

+10
View File
@@ -0,0 +1,10 @@
# AIOps Docker Compose 环境变量
# 复制为 .env 并修改密码
# MySQL 数据库密码(生产环境务必修改)
MYSQL_ROOT_PASSWORD=CHANGE_ME_ROOT_PASSWORD
MYSQL_PASSWORD=CHANGE_ME_USER_PASSWORD
# MySQL 数据库名和用户名(通常无需修改)
MYSQL_DATABASE=aiops
MYSQL_USER=aiops
+1 -1
View File
@@ -21,5 +21,5 @@ DB_NAME=data/aiops.db
# DB_NAME=aiops # DB_NAME=aiops
# JWT 配置 # JWT 配置
JWT_SECRET=aiops-secret-key-change-in-production JWT_SECRET=CHANGE_ME # 生产环境必须替换为随机生成的 32+ 字符密钥
JWT_EXPIRE_HOURS=24 JWT_EXPIRE_HOURS=24
+1 -1
View File
@@ -225,7 +225,7 @@ func loadConfig() *config.Config {
viper.SetDefault("redis.password", "") viper.SetDefault("redis.password", "")
viper.SetDefault("redis.db", 0) viper.SetDefault("redis.db", 0)
viper.SetDefault("jwt.secret", "aiops-jwt-secret-change-in-production") viper.SetDefault("jwt.secret", "") // 生产环境必须通过配置或 JWT_SECRET 环境变量设置
viper.SetDefault("jwt.expire_hour", 24) viper.SetDefault("jwt.expire_hour", 24)
viper.SetDefault("websocket.read_buffer_size", 1024) viper.SetDefault("websocket.read_buffer_size", 1024)
+3 -3
View File
@@ -7,10 +7,10 @@ services:
container_name: ezops-mysql container_name: ezops-mysql
restart: unless-stopped restart: unless-stopped
environment: environment:
MYSQL_ROOT_PASSWORD: rootpassword MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-CHANGE_ME}
MYSQL_DATABASE: ezops MYSQL_DATABASE: ezops
MYSQL_USER: ezops MYSQL_USER: ezops
MYSQL_PASSWORD: ezops123 MYSQL_PASSWORD: ${MYSQL_PASSWORD:-CHANGE_ME}
ports: ports:
- "3306:3306" - "3306:3306"
volumes: volumes:
@@ -54,7 +54,7 @@ services:
- DATABASE_MYSQL_HOST=mysql - DATABASE_MYSQL_HOST=mysql
- DATABASE_MYSQL_PORT=3306 - DATABASE_MYSQL_PORT=3306
- DATABASE_MYSQL_USERNAME=ezops - DATABASE_MYSQL_USERNAME=ezops
- DATABASE_MYSQL_PASSWORD=ezops123 - DATABASE_MYSQL_PASSWORD=${MYSQL_PASSWORD:-CHANGE_ME}
- DATABASE_MYSQL_DATABASE=ezops - DATABASE_MYSQL_DATABASE=ezops
- REDIS_ENABLED=true - REDIS_ENABLED=true
- REDIS_HOST=redis - REDIS_HOST=redis
+4 -4
View File
@@ -7,10 +7,10 @@ services:
container_name: aiops-mysql container_name: aiops-mysql
restart: unless-stopped restart: unless-stopped
environment: environment:
MYSQL_ROOT_PASSWORD: rootpassword MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-CHANGE_ME}
MYSQL_DATABASE: aiops MYSQL_DATABASE: ${MYSQL_DATABASE:-aiops}
MYSQL_USER: aiops MYSQL_USER: ${MYSQL_USER:-aiops}
MYSQL_PASSWORD: aiops123 MYSQL_PASSWORD: ${MYSQL_PASSWORD:-CHANGE_ME}
TZ: Asia/Shanghai TZ: Asia/Shanghai
ports: ports:
- "3306:3306" - "3306:3306"
+40 -36
View File
@@ -17,60 +17,64 @@ interface AgentWizardProps {
onOpenChange: (open: boolean) => void; onOpenChange: (open: boolean) => void;
} }
/** 自动检测服务器地址:使用当前浏览器访问的主机名,端口固定 8080 */ /** 自动检测地址:origin(通过 Nginx 80 代理)用于安装脚本下载,backend 8080 用于 agent 直连 */
function detectServerAddress(): string { function detectServerAddress() {
if (typeof window !== 'undefined' && window.location) { if (typeof window !== 'undefined' && window.location) {
const host = window.location.hostname; return {
return host ? `http://${host}:8080` : 'http://127.0.0.1:8080'; origin: window.location.origin, // http://10.10.10.75 → Nginx
backend: `${window.location.protocol}//${window.location.hostname}:8080`, // http://10.10.10.75:8080 → 后端直达
};
} }
return 'http://127.0.0.1:8080'; return { origin: 'http://127.0.0.1', backend: 'http://127.0.0.1:8080' };
} }
export function AgentWizard({ open, onOpenChange }: AgentWizardProps) { export function AgentWizard({ open, onOpenChange }: AgentWizardProps) {
const token = useUserStore((s) => s.token) || '<YOUR_TOKEN>'; const token = useUserStore((s) => s.token) || '<YOUR_TOKEN>';
const serverUrl = detectServerAddress(); const { origin, backend } = detectServerAddress();
const [copied, setCopied] = useState(false); const [copied, setCopied] = useState(false);
const [showManualInput, setShowManualInput] = useState(false); const [showManualInput, setShowManualInput] = useState(false);
const installCommand = `curl -fsSL ${serverUrl.replace(/^http:/, 'http:')}/agent/install.sh | bash -s -- --server ${serverUrl} --token ${token}`; // install.sh 通过 Nginx 代理下载(origin:80 → /agent/ → backend:8080)
const installCommand = `curl -fsSL ${origin}/agent/install.sh | bash -s -- --server ${backend} --token ${token}`;
// 剪贴板 fallback:execCommand('copy') 兼容 HTTP 环境 // Logan.Li const handleCopy = () => {
const fallbackCopy = (text: string): boolean => {
const textArea = document.createElement('textarea'); const textArea = document.createElement('textarea');
textArea.value = text; textArea.value = installCommand;
// 设置样式让其可见但不可交互,确保浏览器能选中
textArea.style.position = 'fixed'; textArea.style.position = 'fixed';
textArea.style.left = '-9999px'; textArea.style.top = '0';
textArea.style.top = '-9999px'; textArea.style.left = '0';
textArea.style.width = '2em';
textArea.style.height = '2em';
textArea.style.padding = '0';
textArea.style.border = 'none';
textArea.style.outline = 'none';
textArea.style.boxShadow = 'none';
textArea.style.background = 'transparent';
textArea.style.opacity = '0';
textArea.style.pointerEvents = 'none';
textArea.style.zIndex = '-1';
textArea.style.fontSize = '16px';
// iOS 需要 contentEditable + readOnly 兼容
textArea.contentEditable = 'true';
textArea.readOnly = true;
document.body.appendChild(textArea); document.body.appendChild(textArea);
// 选中范围
textArea.focus(); textArea.focus();
textArea.select(); textArea.select();
// 兼容移动端
if (textArea.setSelectionRange) {
textArea.setSelectionRange(0, 999999);
}
let success = false; let success = false;
try { try {
success = document.execCommand('copy'); success = document.execCommand('copy');
} catch { } catch {
// execCommand also failed // execCommand failed
} }
document.body.removeChild(textArea); document.body.removeChild(textArea);
return success;
};
const handleCopy = async () => {
let success = false;
// 方式1: Clipboard API(HTTPS / localhost 可用)
try {
if (navigator.clipboard && window.isSecureContext) {
await navigator.clipboard.writeText(installCommand);
success = true;
}
} catch {
// Clipboard API 不可用,尝试 fallback
}
// 方式2: execCommand fallback(HTTP 环境也可用)
if (!success) {
success = fallbackCopy(installCommand);
}
if (success) { if (success) {
setCopied(true); setCopied(true);
@@ -82,7 +86,7 @@ export function AgentWizard({ open, onOpenChange }: AgentWizardProps) {
} }
}; };
const binaryUrl = `${serverUrl}/agent/aiops-agent-linux-amd64`; const binaryUrl = `${origin}/agent/aiops-agent-linux-amd64`;
const manualSteps = [ const manualSteps = [
{ {
@@ -108,7 +112,7 @@ export function AgentWizard({ open, onOpenChange }: AgentWizardProps) {
``, ``,
`[Service]`, `[Service]`,
`Type=simple`, `Type=simple`,
`ExecStart=/usr/local/bin/aiops-agent --server ${serverUrl} --token ${token}`, `ExecStart=/usr/local/bin/aiops-agent --server ${backend} --token ${token}`,
`Restart=always`, `Restart=always`,
`RestartSec=5`, `RestartSec=5`,
``, ``,
@@ -186,7 +190,7 @@ export function AgentWizard({ open, onOpenChange }: AgentWizardProps) {
<div className="flex items-start gap-2 text-xs text-slate-500"> <div className="flex items-start gap-2 text-xs text-slate-500">
<Terminal className="h-3.5 w-3.5 mt-0.5 shrink-0" /> <Terminal className="h-3.5 w-3.5 mt-0.5 shrink-0" />
<span> <span>
服务器地址已自动检测为当前访问地址({serverUrl})。Agent 安装成功后将自动连接并开始上报指标。 服务器地址已自动检测 — 安装脚本通过 Nginx 代理 ({origin}),Agent 直连后端 ({backend})。安装成功后将自动连接并开始上报指标。
</span> </span>
</div> </div>
</TabsContent> </TabsContent>
+1 -1
View File
@@ -379,7 +379,7 @@ redis:
db: 0 db: 0
jwt: jwt:
secret: "aiops-jwt-secret-change-in-production" secret: "CHANGE_ME_JWT_SECRET" # ⚠️ 生产环境必须替换
expire_hour: 24 expire_hour: 24
websocket: websocket: