- 区分服务端和客户端版本,避免频繁更新客户端

- 优化前端代码
This commit is contained in:
dushixiang committed 2025-11-30 11:38:51 +08:00
1 parent 33e4093461
commit def7b069d2
32 files changed
+262 -212

No files matched your search

+26 -9
View File
@@ -1,11 +1,25 @@
# 版本号(可通过 make VERSION=1.0.0 指定) # 版本号(可通过 make VERSION=1.0.0 指定)
VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo "dev") VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo "dev")
# Agent 版本号基于 pkg/agent/ 目录的最后修改提交生成
# 格式:agent-{最近修改 pkg/agent 的提交短hash}-{提交数量}
AGENT_VERSION ?= $(shell \
LAST_COMMIT=$$(git log -1 --format=%h -- pkg/agent 2>/dev/null || echo "dev"); \
COMMIT_COUNT=$$(git rev-list --count HEAD -- pkg/agent 2>/dev/null || echo "0"); \
if [ "$$LAST_COMMIT" = "dev" ]; then \
echo "dev"; \
else \
echo "agent-$$LAST_COMMIT-$$COMMIT_COUNT"; \
fi \
)
GIT_REVISION=$(shell git rev-parse HEAD) GIT_REVISION=$(shell git rev-parse HEAD)
GO_VERSION=$(shell go version) GO_VERSION=$(shell go version)
BUILD_TIME=$(shell date +%Y-%m-%d_%H:%M:%S) BUILD_TIME=$(shell date +%Y-%m-%d_%H:%M:%S)
# Go 构建参数 # Go 构建参数
LDFLAGS=-s -w -X 'github.com/dushixiang/pika/pkg/version.Version=$(VERSION)' LDFLAGS=-s -w -X 'github.com/dushixiang/pika/pkg/version.Version=$(VERSION)'
AGENT_LDFLAGS=-s -w -X 'github.com/dushixiang/pika/pkg/version.Version=$(VERSION)' -X 'github.com/dushixiang/pika/pkg/version.AgentVersion=$(AGENT_VERSION)'
GOFLAGS=CGO_ENABLED=0 GOFLAGS=CGO_ENABLED=0
# 构建前端 # 构建前端
@@ -26,21 +40,23 @@ build-servers:
# 构建所有平台的 Agent # 构建所有平台的 Agent
build-agents: build-agents:
@echo "Building agents for all platforms (version: $(VERSION))..." @echo "Building agents for all platforms..."
@echo "Server version: $(VERSION)"
@echo "Agent version: $(AGENT_VERSION)"
@mkdir -p bin/agents @mkdir -p bin/agents
# Linux # Linux
$(GOFLAGS) GOOS=linux GOARCH=amd64 go build -ldflags="$(LDFLAGS)" -o bin/agents/pika-agent-linux-amd64 cmd/agent/*.go $(GOFLAGS) GOOS=linux GOARCH=amd64 go build -ldflags="$(AGENT_LDFLAGS)" -o bin/agents/pika-agent-linux-amd64 cmd/agent/*.go
$(GOFLAGS) GOOS=linux GOARCH=arm64 go build -ldflags="$(LDFLAGS)" -o bin/agents/pika-agent-linux-arm64 cmd/agent/*.go $(GOFLAGS) GOOS=linux GOARCH=arm64 go build -ldflags="$(AGENT_LDFLAGS)" -o bin/agents/pika-agent-linux-arm64 cmd/agent/*.go
$(GOFLAGS) GOOS=linux GOARCH=arm GOARM=7 go build -ldflags="$(LDFLAGS)" -o bin/agents/pika-agent-linux-armv7 cmd/agent/*.go $(GOFLAGS) GOOS=linux GOARCH=arm GOARM=7 go build -ldflags="$(AGENT_LDFLAGS)" -o bin/agents/pika-agent-linux-armv7 cmd/agent/*.go
$(GOFLAGS) GOOS=linux GOARCH=loong64 go build -ldflags="$(LDFLAGS)" -o bin/agents/pika-agent-linux-loong64 cmd/agent/*.go $(GOFLAGS) GOOS=linux GOARCH=loong64 go build -ldflags="$(AGENT_LDFLAGS)" -o bin/agents/pika-agent-linux-loong64 cmd/agent/*.go
# macOS # macOS
$(GOFLAGS) GOOS=darwin GOARCH=amd64 go build -ldflags="$(LDFLAGS)" -o bin/agents/pika-agent-darwin-amd64 cmd/agent/*.go $(GOFLAGS) GOOS=darwin GOARCH=amd64 go build -ldflags="$(AGENT_LDFLAGS)" -o bin/agents/pika-agent-darwin-amd64 cmd/agent/*.go
$(GOFLAGS) GOOS=darwin GOARCH=arm64 go build -ldflags="$(LDFLAGS)" -o bin/agents/pika-agent-darwin-arm64 cmd/agent/*.go $(GOFLAGS) GOOS=darwin GOARCH=arm64 go build -ldflags="$(AGENT_LDFLAGS)" -o bin/agents/pika-agent-darwin-arm64 cmd/agent/*.go
# Windows # Windows
$(GOFLAGS) GOOS=windows GOARCH=amd64 go build -ldflags="$(LDFLAGS)" -o bin/agents/pika-agent-windows-amd64.exe cmd/agent/*.go $(GOFLAGS) GOOS=windows GOARCH=amd64 go build -ldflags="$(AGENT_LDFLAGS)" -o bin/agents/pika-agent-windows-amd64.exe cmd/agent/*.go
@echo "All agents built successfully!" @echo "All agents built successfully!"
@echo "Compressing agents with UPX..." @echo "Compressing agents with UPX..."
@@ -80,7 +96,8 @@ wire:
# 显示版本信息 # 显示版本信息
version: version:
@echo "Version: $(VERSION)" @echo "Server Version: $(VERSION)"
@echo "Agent Version: $(AGENT_VERSION)"
@echo "Git Revision: $(GIT_REVISION)" @echo "Git Revision: $(GIT_REVISION)"
@echo "Go Version: $(GO_VERSION)" @echo "Go Version: $(GO_VERSION)"
@echo "Build Time: $(BUILD_TIME)" @echo "Build Time: $(BUILD_TIME)"
+1 -1
View File
@@ -552,7 +552,7 @@ func (h *AgentHandler) GetAgents(c echo.Context) error {
// GetAgentVersion 获取 Agent 版本信息 // GetAgentVersion 获取 Agent 版本信息
func (h *AgentHandler) GetAgentVersion(c echo.Context) error { func (h *AgentHandler) GetAgentVersion(c echo.Context) error {
return orz.Ok(c, orz.Map{ return orz.Ok(c, orz.Map{
"version": version.GetVersion(), "version": version.GetAgentVersion(),
}) })
} }
+2 -2
View File
@@ -587,9 +587,9 @@ func (a *Agent) sendCommandResponse(conn *safeConn, cmdID, cmdType, status, errM
} }
} }
// GetVersion 获取版本号 // GetVersion 获取 Agent 版本号
func GetVersion() string { func GetVersion() string {
return version.GetVersion() return version.GetAgentVersion()
} }
// handleTamperProtect 处理防篡改保护指令(增量更新) // handleTamperProtect 处理防篡改保护指令(增量更新)
+13 -2
View File
@@ -1,12 +1,23 @@
package version package version
// Version 版本号(通过 -ldflags 注入) // Version 服务端版本号(通过 -ldflags 注入)
var Version = "dev" var Version = "dev"
// GetVersion 获取版本号 // AgentVersion Agent 版本号(通过 -ldflags 注入)
var AgentVersion = "dev"
// GetVersion 获取服务端版本号
func GetVersion() string { func GetVersion() string {
if Version == "" { if Version == "" {
return "dev" return "dev"
} }
return Version return Version
} }
// GetAgentVersion 获取 Agent 版本号
func GetAgentVersion() string {
if AgentVersion == "" {
return "dev"
}
return AgentVersion
}
-26
View File
@@ -1,26 +0,0 @@
import { get, post, put } from './request';
import type { NotificationChannel } from '../types';
const PROPERTY_ID_NOTIFICATION_CHANNELS = 'notification_channels';
// 获取通知渠道列表
export const getNotificationChannels = async (): Promise<NotificationChannel[]> => {
const response = await get<{ id: string; name: string; value: NotificationChannel[] }>(
`/admin/properties/${PROPERTY_ID_NOTIFICATION_CHANNELS}`
);
return response.data.value || [];
};
// 保存通知渠道列表
export const saveNotificationChannels = async (channels: NotificationChannel[]): Promise<void> => {
await put(`/admin/properties/${PROPERTY_ID_NOTIFICATION_CHANNELS}`, {
name: '通知渠道配置',
value: channels,
});
};
// 测试通知渠道(从数据库读取配置)
export const testNotificationChannel = async (type: string): Promise<{ message: string }> => {
const response = await post<{ message: string }>(`/admin/notification-channels/${type}/test`);
return response.data;
};
+94
View File
@@ -0,0 +1,94 @@
import { get, post, put } from './request';
// ==================== 通用 Property 接口 ====================
// 通用的 Property 响应类型
export interface PropertyResponse<T> {
id: string;
name: string;
value: T;
}
// 通用的获取 Property 方法
export const getProperty = async <T>(propertyId: string): Promise<T> => {
const response = await get<PropertyResponse<T>>(`/admin/properties/${propertyId}`);
return response.data.value;
};
// 通用的保存 Property 方法
export const saveProperty = async <T>(propertyId: string, name: string, value: T): Promise<void> => {
await put(`/admin/properties/${propertyId}`, {
name,
value,
});
};
// ==================== 指标配置 ====================
const PROPERTY_ID_METRICS_CONFIG = 'metrics_config';
export interface MetricsConfig {
retentionHours: number; // 数据保留时长(小时)
maxQueryPoints: number; // 最大查询点数
}
// 获取指标配置
export const getMetricsConfig = async (): Promise<MetricsConfig> => {
return getProperty<MetricsConfig>(PROPERTY_ID_METRICS_CONFIG);
};
// 保存指标配置
export const saveMetricsConfig = async (config: MetricsConfig): Promise<void> => {
return saveProperty(PROPERTY_ID_METRICS_CONFIG, '指标配置', config);
};
// ==================== 通知渠道配置 ====================
const PROPERTY_ID_NOTIFICATION_CHANNELS = 'notification_channels';
// 通知渠道配置(通过 type 标识,不再使用独立ID)
export interface NotificationChannel {
type: 'dingtalk' | 'wecom' | 'feishu' | 'email' | 'webhook'; // 渠道类型,作为唯一标识
enabled: boolean; // 是否启用
config: Record<string, any>; // JSON配置,根据type不同而不同
}
// 获取通知渠道列表
export const getNotificationChannels = async (): Promise<NotificationChannel[]> => {
const channels = await getProperty<NotificationChannel[]>(PROPERTY_ID_NOTIFICATION_CHANNELS);
return channels || [];
};
// 保存通知渠道列表
export const saveNotificationChannels = async (channels: NotificationChannel[]): Promise<void> => {
return saveProperty(PROPERTY_ID_NOTIFICATION_CHANNELS, '通知渠道配置', channels);
};
// 测试通知渠道(从数据库读取配置)
export const testNotificationChannel = async (type: string): Promise<{ message: string }> => {
const response = await post<{ message: string }>(`/admin/notification-channels/${type}/test`);
return response.data;
};
// ==================== 系统配置 ====================
const PROPERTY_ID_SYSTEM_CONFIG = 'system_config';
export interface SystemConfig {
systemNameEn: string; // 英文名称
systemNameZh: string; // 中文名称
logoBase64: string; // Logo 的 base64 编码
icpCode: string; // ICP 备案号
defaultView: string; // 默认视图 grid,list
}
// 获取系统配置(管理后台使用)
export const getSystemConfig = async (): Promise<SystemConfig> => {
return getProperty<SystemConfig>(PROPERTY_ID_SYSTEM_CONFIG);
};
// 保存系统配置
export const saveSystemConfig = async (config: SystemConfig): Promise<void> => {
return saveProperty(PROPERTY_ID_SYSTEM_CONFIG, '系统配置', config);
};
-27
View File
@@ -1,27 +0,0 @@
import {get, put} from './request';
const PROPERTY_ID_SYSTEM_CONFIG = 'system_config';
export interface SystemConfig {
systemNameEn: string; // 英文名称
systemNameZh: string; // 中文名称
logoBase64: string; // Logo 的 base64 编码
icpCode: string; // ICP 备案号
defaultView: string; // 默认视图 grid,list
}
// 获取系统配置(管理后台使用)
export const getSystemConfig = async (): Promise<SystemConfig> => {
const response = await get<{ id: string; name: string; value: SystemConfig }>(
`/admin/properties/${PROPERTY_ID_SYSTEM_CONFIG}`
);
return response.data.value;
};
// 保存系统配置
export const saveSystemConfig = async (config: SystemConfig): Promise<void> => {
await put(`/admin/properties/${PROPERTY_ID_SYSTEM_CONFIG}`, {
name: '系统配置',
value: config,
});
};
+1 -1
View File
@@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import {Button, Space} from 'antd'; import {Button} from 'antd';
import type {LucideIcon} from 'lucide-react'; import type {LucideIcon} from 'lucide-react';
export interface Action { export interface Action {
+4 -4
View File
@@ -3,10 +3,10 @@ import {Outlet, useLocation, useNavigate} from 'react-router-dom';
import type {MenuProps} from 'antd'; import type {MenuProps} from 'antd';
import {App, Avatar, Button, Dropdown, Space} from 'antd'; import {App, Avatar, Button, Dropdown, Space} from 'antd';
import {Activity, BookOpen, Eye, Key, LogOut, Server, Settings, User as UserIcon} from 'lucide-react'; import {Activity, BookOpen, Eye, Key, LogOut, Server, Settings, User as UserIcon} from 'lucide-react';
import {logout} from '../../api/auth'; import {logout} from '@/api/auth.ts';
import {getServerVersion} from '../../api/agent'; import {getServerVersion} from '@/api/agent.ts';
import type {User} from '../../types'; import type {User} from '@/types';
import {cn} from '../../lib/utils'; import {cn} from '@/lib/utils';
interface NavItem { interface NavItem {
key: string; key: string;
+6 -23
View File
@@ -1,30 +1,13 @@
import {useEffect, useState} from 'react'; import {useEffect, useState} from 'react';
import {useNavigate, useParams, useSearchParams} from 'react-router-dom'; import {useNavigate, useParams, useSearchParams} from 'react-router-dom';
import type {MenuProps, TabsProps} from 'antd'; import type {MenuProps, TabsProps} from 'antd';
import {App, Button, Card, Col, Descriptions, Dropdown, Row, Space, Spin, Tag, Tabs, Alert, Statistic} from 'antd'; import {Alert, App, Button, Card, Descriptions, Dropdown, Space, Spin, Tabs, Tag} from 'antd';
import { import {Activity, ArrowLeft, Clock, FileWarning, RefreshCw, Shield, Terminal} from 'lucide-react';
Activity,
ArrowLeft,
Clock,
RefreshCw,
Shield,
Terminal,
CheckCircle,
XCircle,
AlertTriangle,
TrendingUp,
FileWarning
} from 'lucide-react';
import TamperProtection from './TamperProtection.tsx'; import TamperProtection from './TamperProtection.tsx';
import { import {getAgentForAdmin, getAuditResult, sendAuditCommand, type VPSAuditResult} from '@/api/agent.ts';
getAgentForAdmin, import type {Agent} from '@/types';
getAuditResult,
sendAuditCommand,
type VPSAuditResult
} from '../../api/agent';
import type {Agent} from '../../types';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import {getErrorMessage} from '../../lib/utils'; import {getErrorMessage} from '@/lib/utils';
import AuditResultView from './AuditResultView'; import AuditResultView from './AuditResultView';
const AgentDetail = () => { const AgentDetail = () => {
@@ -235,7 +218,7 @@ const AgentDetail = () => {
</div> </div>
), ),
children: agent?.os.toLowerCase().includes('linux') ? ( children: agent?.os.toLowerCase().includes('linux') ? (
<TamperProtection agentId={agent.id} /> <TamperProtection agentId={agent.id}/>
) : ( ) : (
<Alert <Alert
message="功能限制" message="功能限制"
+3 -3
View File
@@ -1,8 +1,8 @@
import React, {type ReactElement, useCallback, useEffect, useMemo, useState} from 'react'; import React, {type ReactElement, useCallback, useEffect, useMemo, useState} from 'react';
import {Alert, App, Button, Card, Select, Space, Tabs, Typography} from 'antd'; import {Alert, App, Button, Card, Select, Space, Tabs, Typography} from 'antd';
import {CopyIcon} from 'lucide-react'; import {CopyIcon} from 'lucide-react';
import {listApiKeys} from '../../api/apiKey'; import {listApiKeys} from '@/api/apiKey.ts';
import type {ApiKey} from '../../types'; import type {ApiKey} from '@/types';
import linuxPng from '../../assets/os/linux.png'; import linuxPng from '../../assets/os/linux.png';
import applePng from '../../assets/os/apple.png'; import applePng from '../../assets/os/apple.png';
import windowsPng from '../../assets/os/win11.png'; import windowsPng from '../../assets/os/win11.png';
@@ -377,7 +377,7 @@ curl -L ${serverUrl}${config.downloadUrl} -o ${AGENT_NAME}`
</div> </div>
<Tabs <Tabs
style={{ marginTop: 24 }} style={{marginTop: 24}}
tabPosition="left" tabPosition="left"
items={tabItems} items={tabItems}
/> />
+4 -4
View File
@@ -5,11 +5,11 @@ import {ProTable} from '@ant-design/pro-components';
import {App, Button, DatePicker, Divider, Dropdown, Form, Input, Modal, Select, Space, Tag} from 'antd'; import {App, Button, DatePicker, Divider, Dropdown, Form, Input, Modal, Select, Space, Tag} from 'antd';
import type {MenuProps} from 'antd'; import type {MenuProps} from 'antd';
import {Edit, Eye, RefreshCw, Plus, Shield, Trash2, MoreVertical} from 'lucide-react'; import {Edit, Eye, RefreshCw, Plus, Shield, Trash2, MoreVertical} from 'lucide-react';
import {deleteAgent, getAgentPaging, getTags, updateAgentInfo} from '../../api/agent'; import {deleteAgent, getAgentPaging, getTags, updateAgentInfo} from '@/api/agent.ts';
import type {Agent} from '../../types'; import type {Agent} from '@/types';
import {getErrorMessage} from '../../lib/utils'; import {getErrorMessage} from '@/lib/utils';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import {PageHeader} from '../../components'; import {PageHeader} from '@/components';
const AgentList = () => { const AgentList = () => {
const navigate = useNavigate(); const navigate = useNavigate();
+40 -26
View File
@@ -1,31 +1,29 @@
import {Card, Collapse, List, Space, Tag, Descriptions, Tabs, Table, Empty, Alert} from 'antd'; import {Alert, Card, Descriptions, Empty, List, Space, Table, Tabs, Tag} from 'antd';
import { import {
CheckCircle,
XCircle,
AlertTriangle,
MinusCircle,
Server,
Network,
Cpu,
Users,
FileText,
Shield,
Activity, Activity,
Calendar,
Settings,
PlayCircle,
AlertOctagon, AlertOctagon,
LogIn AlertTriangle,
Calendar,
CheckCircle,
Cpu,
FileText,
LogIn,
MinusCircle,
Network,
PlayCircle,
Server,
Settings,
Shield,
Users,
XCircle
} from 'lucide-react'; } from 'lucide-react';
import type {VPSAuditResult, ProcessInfo, FileInfo, LoginRecord, LoginSession, SSHKeyInfo, SudoUserInfo} from '../../api/agent'; import type {ProcessInfo, VPSAuditResult} from '@/api/agent.ts';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import duration from 'dayjs/plugin/duration'; import duration from 'dayjs/plugin/duration';
import React from 'react'; import React from 'react';
dayjs.extend(duration); dayjs.extend(duration);
const {Panel} = Collapse;
interface RiskItem { interface RiskItem {
level: 'critical' | 'high' | 'medium' | 'low'; level: 'critical' | 'high' | 'medium' | 'low';
title: string; title: string;
@@ -472,7 +470,8 @@ const AuditResultView = ({result}: AuditResultViewProps) => {
{/* 风险概览 */} {/* 风险概览 */}
{risks.length > 0 && ( {risks.length > 0 && (
<Card <Card
title={<Space><AlertOctagon size={18} className="text-red-500"/><span className="font-semibold text-red-500">风险概览</span></Space>} title={<Space><AlertOctagon size={18} className="text-red-500"/><span
className="font-semibold text-red-500">风险概览</span></Space>}
className="border-red-200 bg-red-50" className="border-red-200 bg-red-50"
> >
<List <List
@@ -592,7 +591,8 @@ const AuditResultView = ({result}: AuditResultViewProps) => {
showIcon showIcon
/> />
)} )}
<Card size="small" title={<Space><AlertTriangle size={16} className={result.assetInventory.processAssets?.suspiciousProcesses?.length ? "text-red-500" : ""}/>可疑进程</Space>}> <Card size="small" title={<Space><AlertTriangle size={16}
className={result.assetInventory.processAssets?.suspiciousProcesses?.length ? "text-red-500" : ""}/>可疑进程</Space>}>
{result.assetInventory.processAssets?.suspiciousProcesses?.length ? ( {result.assetInventory.processAssets?.suspiciousProcesses?.length ? (
<Table <Table
size="small" size="small"
@@ -602,7 +602,8 @@ const AuditResultView = ({result}: AuditResultViewProps) => {
{ {
title: '异常信息', title: '异常信息',
key: 'issue', key: 'issue',
render: (record: ProcessInfo) => record.exeDeleted ? <Tag color="error">Exe已删除</Tag> : null render: (record: ProcessInfo) => record.exeDeleted ?
<Tag color="error">Exe已删除</Tag> : null
} }
]} ]}
rowKey="pid" rowKey="pid"
@@ -767,15 +768,26 @@ const AuditResultView = ({result}: AuditResultViewProps) => {
showIcon showIcon
/> />
)} )}
<Card size="small" title={<Space><AlertTriangle size={16} className={result.assetInventory.fileAssets?.tmpExecutables?.length ? "text-red-500" : ""}/>临时目录可执行文件</Space>}> <Card size="small" title={<Space><AlertTriangle size={16}
className={result.assetInventory.fileAssets?.tmpExecutables?.length ? "text-red-500" : ""}/>临时目录可执行文件</Space>}>
{result.assetInventory.fileAssets?.tmpExecutables?.length ? ( {result.assetInventory.fileAssets?.tmpExecutables?.length ? (
<Table <Table
size="small" size="small"
dataSource={result.assetInventory.fileAssets.tmpExecutables} dataSource={result.assetInventory.fileAssets.tmpExecutables}
columns={[ columns={[
{title: '路径', dataIndex: 'path', key: 'path'}, {title: '路径', dataIndex: 'path', key: 'path'},
{title: '大小', dataIndex: 'size', key: 'size', render: (val: number) => `${(val / 1024).toFixed(2)} KB`}, {
{title: '修改时间', dataIndex: 'modTime', key: 'modTime', render: (val: number) => dayjs(val).format('YYYY-MM-DD HH:mm:ss')}, title: '大小',
dataIndex: 'size',
key: 'size',
render: (val: number) => `${(val / 1024).toFixed(2)} KB`
},
{
title: '修改时间',
dataIndex: 'modTime',
key: 'modTime',
render: (val: number) => dayjs(val).format('YYYY-MM-DD HH:mm:ss')
},
{title: '权限', dataIndex: 'permissions', key: 'permissions'}, {title: '权限', dataIndex: 'permissions', key: 'permissions'},
]} ]}
rowKey="path" rowKey="path"
@@ -825,7 +837,8 @@ const AuditResultView = ({result}: AuditResultViewProps) => {
pagination={false} pagination={false}
/> />
) : ( ) : (
<Empty description={showAllServices ? "无Systemd服务" : "无活跃的Systemd服务"}/> <Empty
description={showAllServices ? "无Systemd服务" : "无活跃的Systemd服务"}/>
)} )}
</Card> </Card>
<Card size="small" title={<Space><PlayCircle size={16}/>启动脚本</Space>}> <Card size="small" title={<Space><PlayCircle size={16}/>启动脚本</Space>}>
@@ -870,7 +883,8 @@ const AuditResultView = ({result}: AuditResultViewProps) => {
<Empty description="无登录历史"/> <Empty description="无登录历史"/>
)} )}
</Card> </Card>
<Card size="small" title={<Space><AlertTriangle size={16} className={result.assetInventory.loginAssets?.failedLogins?.length ? "text-yellow-500" : ""}/>失败登录历史</Space>}> <Card size="small" title={<Space><AlertTriangle size={16}
className={result.assetInventory.loginAssets?.failedLogins?.length ? "text-yellow-500" : ""}/>失败登录历史</Space>}>
{result.assetInventory.loginAssets?.failedLogins?.length ? ( {result.assetInventory.loginAssets?.failedLogins?.length ? (
<Table <Table
size="small" size="small"
+1 -1
View File
@@ -8,7 +8,7 @@ import {
type TamperConfig, type TamperConfig,
type TamperEvent, type TamperEvent,
updateTamperConfig updateTamperConfig
} from '../../api/tamper.ts'; } from '@/api/tamper.ts';
import {App} from "antd"; import {App} from "antd";
interface TamperProtectionProps { interface TamperProtectionProps {
+4 -4
View File
@@ -10,11 +10,11 @@ import {
generateApiKey, generateApiKey,
listApiKeys, listApiKeys,
updateApiKeyName, updateApiKeyName,
} from '../../api/apiKey'; } from '@/api/apiKey.ts';
import type {ApiKey, GenerateApiKeyRequest} from '../../types'; import type {ApiKey, GenerateApiKeyRequest} from '@/types';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import {getErrorMessage} from '../../lib/utils'; import {getErrorMessage} from '@/lib/utils';
import {PageHeader} from '../../components'; import {PageHeader} from '@/components';
const ApiKeyList = () => { const ApiKeyList = () => {
const {message: messageApi} = App.useApp(); const {message: messageApi} = App.useApp();
+1 -1
View File
@@ -1,7 +1,7 @@
import {useEffect} from 'react'; import {useEffect} from 'react';
import {useNavigate, useSearchParams} from 'react-router-dom'; import {useNavigate, useSearchParams} from 'react-router-dom';
import {App} from 'antd'; import {App} from 'antd';
import {githubLogin} from '../../api/auth'; import {githubLogin} from '@/api/auth.ts';
const GitHubCallback = () => { const GitHubCallback = () => {
const navigate = useNavigate(); const navigate = useNavigate();
+1 -1
View File
@@ -1,7 +1,7 @@
import {useEffect} from 'react'; import {useEffect} from 'react';
import {useNavigate, useSearchParams} from 'react-router-dom'; import {useNavigate, useSearchParams} from 'react-router-dom';
import {App} from 'antd'; import {App} from 'antd';
import {oidcLogin} from '../../api/auth'; import {oidcLogin} from '@/api/auth.ts';
const OIDCCallback = () => { const OIDCCallback = () => {
const navigate = useNavigate(); const navigate = useNavigate();
+2 -3
View File
@@ -2,9 +2,8 @@ import {useEffect, useState} from 'react';
import {useNavigate} from 'react-router-dom'; import {useNavigate} from 'react-router-dom';
import {App, Button, Form, Input} from 'antd'; import {App, Button, Form, Input} from 'antd';
import {GithubOutlined, GlobalOutlined, LockOutlined, UserOutlined} from '@ant-design/icons'; import {GithubOutlined, GlobalOutlined, LockOutlined, UserOutlined} from '@ant-design/icons';
// 移除不需要的图标组件,保持轻量 import {getAuthConfig, getGitHubAuthURL, getOIDCAuthURL, login} from '@/api/auth.ts';
import {getAuthConfig, getGitHubAuthURL, getOIDCAuthURL, login} from '../../api/auth'; import type {LoginRequest} from '@/types';
import type {LoginRequest} from '../../types';
const Login = () => { const Login = () => {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
+5 -5
View File
@@ -2,13 +2,13 @@ import {useCallback, useEffect, useMemo, useRef, useState} from 'react';
import type {ActionType, ProColumns} from '@ant-design/pro-components'; import type {ActionType, ProColumns} from '@ant-design/pro-components';
import {ProTable} from '@ant-design/pro-components'; import {ProTable} from '@ant-design/pro-components';
import {App, Button, Divider, Form, Input, InputNumber, Modal, Select, Space, Switch, Tag,} from 'antd'; import {App, Button, Divider, Form, Input, InputNumber, Modal, Select, Space, Switch, Tag,} from 'antd';
import {PageHeader} from '../../components'; import {PageHeader} from '@/components';
import {Edit, MinusCircle, Plus, PlusCircle, RefreshCw, Trash2} from 'lucide-react'; import {Edit, MinusCircle, Plus, PlusCircle, RefreshCw, Trash2} from 'lucide-react';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import {getAgentPaging, getTags} from '../../api/agent'; import {getAgentPaging, getTags} from '@/api/agent.ts';
import type {Agent, MonitorTask, MonitorTaskRequest} from '../../types'; import type {Agent, MonitorTask, MonitorTaskRequest} from '@/types';
import {createMonitor, deleteMonitor, listMonitors, updateMonitor} from '../../api/monitor'; import {createMonitor, deleteMonitor, listMonitors, updateMonitor} from '@/api/monitor.ts';
import {getErrorMessage} from '../../lib/utils'; import {getErrorMessage} from '@/lib/utils';
const HTTP_METHODS = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS']; const HTTP_METHODS = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'];
+2 -2
View File
@@ -15,8 +15,8 @@ import {
} from 'lucide-react'; } from 'lucide-react';
import type {TooltipProps} from 'recharts'; import type {TooltipProps} from 'recharts';
import {Area, AreaChart, CartesianGrid, Legend, ResponsiveContainer, Tooltip, XAxis, YAxis,} from 'recharts'; import {Area, AreaChart, CartesianGrid, Legend, ResponsiveContainer, Tooltip, XAxis, YAxis,} from 'recharts';
import {type AggregatedMonitorMetric, getMonitorHistory, getMonitorStatsById} from '../../api/monitor'; import {type AggregatedMonitorMetric, getMonitorHistory, getMonitorStatsById} from '@/api/monitor.ts';
import type {MonitorStats} from '../../types'; import type {MonitorStats} from '@/types';
const formatTime = (ms: number): string => { const formatTime = (ms: number): string => {
if (!ms || ms <= 0) return '0 ms'; if (!ms || ms <= 0) return '0 ms';
+2 -2
View File
@@ -2,8 +2,8 @@ import {useEffect} from 'react';
import {useNavigate} from 'react-router-dom'; import {useNavigate} from 'react-router-dom';
import {useQuery} from '@tanstack/react-query'; import {useQuery} from '@tanstack/react-query';
import {AlertCircle, CheckCircle2, Clock, Loader2, Shield} from 'lucide-react'; import {AlertCircle, CheckCircle2, Clock, Loader2, Shield} from 'lucide-react';
import {getPublicMonitors} from '../../api/monitor'; import {getPublicMonitors} from '@/api/monitor.ts';
import type {PublicMonitor} from '../../types'; import type {PublicMonitor} from '@/types';
import {usePublicLayout} from '../PublicLayout'; import {usePublicLayout} from '../PublicLayout';
const formatTime = (ms: number): string => { const formatTime = (ms: number): string => {
+5 -7
View File
@@ -1,7 +1,6 @@
import React, {type ReactNode, useEffect, useMemo, useState} from 'react'; import React, {type ReactNode, useEffect, useMemo, useState} from 'react';
import {useNavigate, useParams} from 'react-router-dom'; import {useNavigate, useParams} from 'react-router-dom';
import { import {
Activity,
ArrowLeft, ArrowLeft,
Cpu, Cpu,
HardDrive, HardDrive,
@@ -33,19 +32,18 @@ import {
type GetAgentMetricsRequest, type GetAgentMetricsRequest,
getNetworkMetricsByInterface, getNetworkMetricsByInterface,
type NetworkMetricByInterface type NetworkMetricByInterface
} from '../../api/agent'; } from '@/api/agent.ts';
import type { import type {
Agent, Agent,
AggregatedCPUMetric, AggregatedCPUMetric,
AggregatedDiskIOMetric, AggregatedDiskIOMetric,
AggregatedDiskMetric,
AggregatedGPUMetric, AggregatedGPUMetric,
AggregatedMemoryMetric, AggregatedMemoryMetric,
AggregatedNetworkConnectionMetric, AggregatedNetworkConnectionMetric,
AggregatedNetworkMetric, AggregatedNetworkMetric,
AggregatedTemperatureMetric, AggregatedTemperatureMetric,
LatestMetrics LatestMetrics
} from '../../types'; } from '@/types';
const formatBytes = (bytes: number | undefined | null): string => { const formatBytes = (bytes: number | undefined | null): string => {
if (!bytes || bytes <= 0) return '0 B'; if (!bytes || bytes <= 0) return '0 B';
@@ -256,7 +254,7 @@ type MetricsState = {
memory: AggregatedMemoryMetric[]; memory: AggregatedMemoryMetric[];
network: AggregatedNetworkMetric[]; network: AggregatedNetworkMetric[];
networkConnection: AggregatedNetworkConnectionMetric[]; networkConnection: AggregatedNetworkConnectionMetric[];
disk: AggregatedDiskMetric[]; // disk: AggregatedDiskMetric[];
diskIO: AggregatedDiskIOMetric[]; diskIO: AggregatedDiskIOMetric[];
gpu: AggregatedGPUMetric[]; gpu: AggregatedGPUMetric[];
temperature: AggregatedTemperatureMetric[]; temperature: AggregatedTemperatureMetric[];
@@ -267,7 +265,7 @@ const createEmptyMetricsState = (): MetricsState => ({
memory: [], memory: [],
network: [], network: [],
networkConnection: [], networkConnection: [],
disk: [], // disk: [],
diskIO: [], diskIO: [],
gpu: [], gpu: [],
temperature: [], temperature: [],
@@ -278,7 +276,7 @@ const metricRequestConfig: Array<{ key: keyof MetricsState; type: GetAgentMetric
{key: 'memory', type: 'memory'}, {key: 'memory', type: 'memory'},
{key: 'network', type: 'network'}, {key: 'network', type: 'network'},
{key: 'networkConnection', type: 'network_connection'}, {key: 'networkConnection', type: 'network_connection'},
{key: 'disk', type: 'disk'}, // {key: 'disk', type: 'disk'},
{key: 'diskIO', type: 'disk_io'}, {key: 'diskIO', type: 'disk_io'},
{key: 'gpu', type: 'gpu'}, {key: 'gpu', type: 'gpu'},
{key: 'temperature', type: 'temperature'}, {key: 'temperature', type: 'temperature'},
+2 -2
View File
@@ -2,8 +2,8 @@ import {type ReactNode, useEffect, useState} from 'react';
import {Link, useNavigate} from 'react-router-dom'; import {Link, useNavigate} from 'react-router-dom';
import {useQuery} from '@tanstack/react-query'; import {useQuery} from '@tanstack/react-query';
import {Cpu, EthernetPortIcon, HardDrive, Loader2, MemoryStick, Network} from 'lucide-react'; import {Cpu, EthernetPortIcon, HardDrive, Loader2, MemoryStick, Network} from 'lucide-react';
import {listAgents, getPublicTags} from '../../api/agent'; import {listAgents, getPublicTags} from '@/api/agent.ts';
import type {Agent, LatestMetrics} from '../../types'; import type {Agent, LatestMetrics} from '@/types';
import {usePublicLayout} from '../PublicLayout'; import {usePublicLayout} from '../PublicLayout';
interface AgentWithMetrics extends Agent { interface AgentWithMetrics extends Agent {
+3 -3
View File
@@ -2,9 +2,9 @@ import {useEffect} from 'react';
import {App, Button, Card, Form, InputNumber, Space, Switch} from 'antd'; import {App, Button, Card, Form, InputNumber, Space, Switch} from 'antd';
import {Save} from 'lucide-react'; import {Save} from 'lucide-react';
import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'; import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query';
import type {AlertConfig} from '../../types'; import type {AlertConfig} from '@/types';
import {createAlertConfig, getAlertConfigsByAgent, updateAlertConfig} from '../../api/alert'; import {createAlertConfig, getAlertConfigsByAgent, updateAlertConfig} from '@/api/alert.ts';
import {getErrorMessage} from '../../lib/utils'; import {getErrorMessage} from '@/lib/utils';
const AlertSettings = () => { const AlertSettings = () => {
const [form] = Form.useForm(); const [form] = Form.useForm();
+3 -3
View File
@@ -2,9 +2,9 @@ import {useEffect} from 'react';
import {App, Button, Card, Form, InputNumber, Space, Spin} from 'antd'; import {App, Button, Card, Form, InputNumber, Space, Spin} from 'antd';
import {Database, Clock, BarChart3} from 'lucide-react'; import {Database, Clock, BarChart3} from 'lucide-react';
import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'; import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query';
import type {MetricsConfig} from '../../api/metrics-config'; import type {MetricsConfig} from '@/api/property.ts';
import {getMetricsConfig, saveMetricsConfig} from '../../api/metrics-config'; import {getMetricsConfig, saveMetricsConfig} from '@/api/property.ts';
import {getErrorMessage} from '../../lib/utils'; import {getErrorMessage} from '@/lib/utils';
const MetricsConfigComponent = () => { const MetricsConfigComponent = () => {
const [form] = Form.useForm(); const [form] = Form.useForm();
@@ -2,13 +2,13 @@ import {useEffect} from 'react';
import {App, Button, Card, Collapse, Form, Input, Select, Space, Spin, Switch} from 'antd'; import {App, Button, Card, Collapse, Form, Input, Select, Space, Spin, Switch} from 'antd';
import {TestTube} from 'lucide-react'; import {TestTube} from 'lucide-react';
import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'; import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query';
import type {NotificationChannel} from '../../types';
import { import {
getNotificationChannels, getNotificationChannels,
type NotificationChannel,
saveNotificationChannels, saveNotificationChannels,
testNotificationChannel, testNotificationChannel,
} from '../../api/notification-channel'; } from '@/api/property.ts';
import {getErrorMessage} from '../../lib/utils'; import {getErrorMessage} from '@/lib/utils';
const NotificationChannels = () => { const NotificationChannels = () => {
const [form] = Form.useForm(); const [form] = Form.useForm();
+3 -3
View File
@@ -2,9 +2,9 @@ import {useEffect, useState} from 'react';
import {App, Button, Card, Form, Input, Radio, Space, Spin, Upload} from 'antd'; import {App, Button, Card, Form, Input, Radio, Space, Spin, Upload} from 'antd';
import {Upload as UploadIcon, Grid3x3, List} from 'lucide-react'; import {Upload as UploadIcon, Grid3x3, List} from 'lucide-react';
import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'; import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query';
import type {SystemConfig} from '../../api/system-config'; import type {SystemConfig} from '@/api/property.ts';
import {getSystemConfig, saveSystemConfig} from '../../api/system-config'; import {getSystemConfig, saveSystemConfig} from '@/api/property.ts';
import {getErrorMessage} from '../../lib/utils'; import {getErrorMessage} from '@/lib/utils.ts';
import type {RcFile} from 'antd/es/upload/interface'; import type {RcFile} from 'antd/es/upload/interface';
const SystemConfigComponent = () => { const SystemConfigComponent = () => {
+2 -2
View File
@@ -1,10 +1,10 @@
import {Tabs} from 'antd'; import {Tabs} from 'antd';
import {Bell, MessageSquare, Settings2, Database} from 'lucide-react'; import {Bell, Database, MessageSquare, Settings2} from 'lucide-react';
import AlertSettings from './AlertSettings'; import AlertSettings from './AlertSettings';
import NotificationChannels from './NotificationChannels'; import NotificationChannels from './NotificationChannels';
import SystemConfig from './SystemConfig'; import SystemConfig from './SystemConfig';
import MetricsConfig from './MetricsConfig'; import MetricsConfig from './MetricsConfig';
import {PageHeader} from "../../components"; import {PageHeader} from "@/components";
const Settings = () => { const Settings = () => {
const items = [ const items = [
-39
View File
@@ -443,45 +443,6 @@ export interface AlertConfig {
updatedAt?: number; updatedAt?: number;
} }
// 通知渠道配置(通过 type 标识,不再使用独立ID)
export interface NotificationChannel {
type: 'dingtalk' | 'wecom' | 'feishu' | 'email' | 'webhook'; // 渠道类型,作为唯一标识
enabled: boolean; // 是否启用
config: Record<string, any>; // JSON配置,根据type不同而不同
}
// 各种通知渠道的配置类型
export interface DingTalkConfig {
secretKey: string; // 访问令牌
signSecret?: string; // 加签密钥(可选)
}
export interface WeComConfig {
secretKey: string; // Webhook Key
}
export interface FeishuConfig {
secretKey: string; // Webhook Token
signSecret?: string; // 签名密钥(可选)
}
export interface WebhookConfig {
url: string; // 自定义URL
method?: string; // HTTP方法:GET, POST, PUT, PATCH, DELETE,默认POST
headers?: Record<string, string>; // 自定义请求头
bodyTemplate?: 'json' | 'form' | 'custom'; // 请求体模板类型
customBody?: string; // 自定义请求体模板(当 bodyTemplate 为 custom 时使用)
}
export interface EmailConfig {
smtpHost: string;
smtpPort: number;
username: string;
password: string;
from: string;
to: string[];
}
export interface AlertRecord { export interface AlertRecord {
id: number; id: number;
agentId: string; agentId: string;
+8
View File
@@ -15,6 +15,14 @@
"moduleDetection": "force", "moduleDetection": "force",
"noEmit": true, "noEmit": true,
"jsx": "react-jsx", "jsx": "react-jsx",
"baseUrl": ".",
"paths": {
"@/*": [
"./src/*"
]
},
"allowSyntheticDefaultImports": true
}, },
"include": ["src"] "include": ["src"]
} }
+15 -3
View File
@@ -1,7 +1,19 @@
{ {
"files": [], "files": [],
"references": [ "references": [
{ "path": "./tsconfig.app.json" }, {
{ "path": "./tsconfig.node.json" } "path": "./tsconfig.app.json"
] },
{
"path": "./tsconfig.node.json"
}
],
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": [
"./src/*"
]
}
}
} }
+6
View File
@@ -1,6 +1,7 @@
import {defineConfig} from 'vite' import {defineConfig} from 'vite'
import react from '@vitejs/plugin-react' import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite' import tailwindcss from '@tailwindcss/vite'
import path from "path";
// https://vite.dev/config/ // https://vite.dev/config/
export default defineConfig({ export default defineConfig({
@@ -8,6 +9,11 @@ export default defineConfig({
react(), react(),
tailwindcss(), tailwindcss(),
], ],
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
server: { server: {
proxy: { proxy: {
'/api/': { '/api/': {