Compare commits

...

17 Commits

Author SHA1 Message Date
yyhuni
5de7ea9dbc 优化:利用docker hub加速构建 2025-12-18 22:56:58 +08:00
github-actions[bot]
971641cdeb chore: bump version to v1.0.10 2025-12-18 14:52:35 +00:00
poem
e5a74faf9f 删除:旧文档 2025-12-18 22:35:39 +08:00
poem
e9a58e89aa 更新:前端表格字段命名,后端注释更新 2025-12-18 22:31:55 +08:00
rongxinrou
3d9d520dc7 新增dev下进行worker构建,并使用本地构建的worker镜像启动容器 2025-12-18 21:54:54 +08:00
yyhuni
8d814b5864 fix: 日志逻辑 2025-12-18 20:39:53 +08:00
rongxinrou
c16b7afabe 增加日志 2025-12-18 20:30:25 +08:00
rongxinrou
fa55167989 修复端口扫描IP时的问题 2025-12-18 20:16:21 +08:00
yyhuni
55a2762c71 fix: 证书兼容性 2025-12-18 18:53:28 +08:00
github-actions[bot]
5532f1e63a chore: bump version to v1.0.9 2025-12-18 10:51:53 +00:00
yyhuni
948568e950 更新注释 2025-12-18 18:42:37 +08:00
yyhuni
873b6893f1 fix version 2025-12-18 18:32:20 +08:00
yyhuni
dbb30f7c78 chore: bump version to v1.0.9 2025-12-18 18:29:35 +08:00
yyhuni
38eced3814 fix: Prefect 本地模式配置:禁用 API server 和事件系统 2025-12-18 18:28:19 +08:00
yyhuni
68fc7cee3b fix: 兼容旧版本证书安装,兼容旧版本docker compose 2025-12-18 17:44:02 +08:00
yyhuni
6e23824a45 todo:待接入add_command_to_scan 2025-12-17 21:40:07 +08:00
github-actions[bot]
a88cceb4f4 chore: bump version to v1.0.8 2025-12-17 11:09:39 +00:00
21 changed files with 172 additions and 346 deletions

View File

@@ -100,8 +100,12 @@ jobs:
tags: |
${{ env.IMAGE_PREFIX }}/${{ matrix.image }}:${{ steps.version.outputs.VERSION }}
${{ steps.version.outputs.IS_RELEASE == 'true' && format('{0}/{1}:latest', env.IMAGE_PREFIX, matrix.image) || '' }}
cache-from: type=gha
cache-to: type=gha,mode=max
cache-from: |
type=gha
type=registry,ref=${{ env.IMAGE_PREFIX }}/${{ matrix.image }}:buildcache
cache-to: |
type=gha,mode=max
type=registry,ref=${{ env.IMAGE_PREFIX }}/${{ matrix.image }}:buildcache,mode=max
# 所有镜像构建成功后,更新 VERSION 文件
update-version:

View File

@@ -1,12 +0,0 @@
---
trigger: always_on
---
1.后端网页应该是 8888 端口
3.前端所有路由加上末尾斜杠,以匹配 django 的 DRF 规则
4.网页测试可以用 curl
8.所有前端 api 接口都应该写在@services 中,所有 type 类型都应该写在@types
10.前端的加载等逻辑用 React Query来实现自动管理
17.所有业务操作的 toast 都放在 hook 中
23.前端非必要不要采用window.location.href去跳转而是用Next.js 客户端路由
24.ui相关的都去调用mcp来看看有没有通用组件美观的组件来实现

View File

@@ -1,85 +0,0 @@
---
trigger: manual
description: 进行代码审查的时候,必须调用这个规则
---
### **0. 逻辑正确性 & Bug 排查** *(最高优先级,必须手动推演)*
**目标**:不依赖测试,主动发现“代码能跑但结果错”的逻辑错误。
1. **手动推演关键路径**
- 选 2~3 个典型输入(含边界),**在脑中或纸上一步步推演代码执行流程**。
- 输出是否符合预期?每一步变量变化是否正确?
2. **常见逻辑 bug 检查**
- **off-by-one**:循环、数组索引、分页
- **条件逻辑错误**`and`/`or` 优先级、短路求值误用
- **状态混乱**:变量未初始化、被意外覆盖
- **算法偏差**:排序、搜索、二分查找的中点处理
- **浮点精度**:是否误用 `==` 比较浮点数?
3. **控制流审查**
- 所有 `if/else` 分支是否都覆盖?有无“不可达代码”?
- `switch`/`match` 是否有 `default`?是否漏 case
- 异常路径会返回什么?是否遗漏 `finally` 清理?
4. **业务逻辑一致性**
- 是否符合**业务规则**?(如“订单总额 = 商品价 × 数量 + 运费 - 折扣”)
- 是否遗漏隐含约束?(如“用户只能评价已完成的订单”)
### **一、功能性 & 正确性** *(阻塞性问题必须修复)*
1. **需求符合度**是否100%覆盖需求?遗漏/多余功能点?
2. **边界条件**
- 输入:`null`、空、极值、非法格式
- 集合空、单元素、超大如10⁶
- 循环终止条件、off-by-one
3. **错误处理**
- 异常捕获全面?失败路径有降级?
- 错误信息清晰?不泄露栈迹?
4. **并发安全**
- 竞态/死锁风险?共享资源是否同步?
- 使用了`volatile`/`synchronized`/`Lock`/`atomic`
5. **单元测试**
- 覆盖率 ≥80%?包含正向/边界/异常用例?
- 测试独立?无外部依赖?
### **二、代码质量与可读性**
1. **命名**:见名知意?遵循规范?
2. **函数设计**
- **单一职责**?参数 ≤4建议长度 <50行视语言调整
- 可提取为工具函数?
3. **结构与复杂度**
- 无重复代码?圈复杂度 <10
- 嵌套 ≤3层使用卫语句提前返回
4. **注释**:解释**为什么**而非**是什么**?复杂逻辑必注释
5. **风格一致**:通过`Prettier`/`ESLint`/`Spotless`自动格式化
### **三、架构与设计**
1. **SOLID**:是否符合单一职责、开闭、依赖倒置?
2. **依赖**:是否依赖接口而非实现?无循环依赖?
3. **可测试性**:是否支持依赖注入?避免`new`硬编码
4. **扩展性**:新增功能是否只需改一处?
### **四、性能优化**
- **N+1查询**循环内IO/日志/分配?
- 算法复杂度合理如O(n²)是否可优化)
- 内存:无泄漏?大对象及时释放?缓存有失效?
### **五、其他**
1. **可维护性**:日志带上下文?修改后更干净?
2. **兼容性**API/数据库变更是否向后兼容?
3. **依赖管理**:新库必要?许可证合规?
---
### **审查最佳实践**
- **小批次审查**≤200行/次
- **语气建议**`“建议将函数拆分以提升可读性”` 而非 `“这个函数太长了”`
- **自动化先行**:风格/空指针/安全扫描 → CI工具
- **重点分级**
- 🛑 **阻塞**:功能错、安全漏洞
- ⚠️ **必须改**:设计缺陷、性能瓶颈
- 💡 **建议**:风格、命名、可读性

View File

@@ -1,195 +0,0 @@
---
trigger: always_on
---
## 标准分层架构调用顺序
按照 **DDD领域驱动设计和清洁架构**原则,调用顺序应该是:
```
HTTP请求 → Views → Tasks → Services → Repositories → Models
```
---
### 📊 完整的调用链路图
```
┌─────────────────────────────────────────────────────────────┐
│ HTTP Request (前端) │
└────────────────────────┬────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Views (HTTP 层) │
│ - 参数验证 │
│ - 权限检查 │
│ - 调用 Tasks/Services │
│ - 返回 HTTP 响应 │
└────────────────────────┬────────────────────────────────────┘
┌────────────────┴────────────────┐
↓ (异步) ↓ (同步)
┌──────────────────┐ ┌──────────────────┐
│ Tasks (任务层) │ │ Services (业务层)│
│ - 异步执行 │ │ - 业务逻辑 │
│ - 后台作业 │───────>│ - 事务管理 │
│ - 通知发送 │ │ - 数据验证 │
└──────────────────┘ └────────┬─────────┘
┌──────────────────────┐
│ Repositories (存储层) │
│ - 数据访问 │
│ - 查询封装 │
│ - 批量操作 │
└────────┬─────────────┘
┌──────────────────────┐
│ Models (模型层) │
│ - ORM 定义 │
│ - 数据结构 │
│ - 关系映射 │
└──────────────────────┘
```
---
### 🔄 具体调用示例
### **场景 1同步删除Views → Services → Repositories → Models**
```python
# 1. Views 层 (views.py)
def some_sync_delete(self, request):
# 参数验证
target_ids = request.data.get('ids')
# 调用 Service 层
service = TargetService()
result = service.bulk_delete_targets(target_ids)
# 返回响应
return Response({'message': 'deleted'})
# 2. Services 层 (services/target_service.py)
class TargetService:
def bulk_delete_targets(self, target_ids):
# 业务逻辑验证
logger.info("准备删除...")
# 调用 Repository 层
deleted_count = self.repo.bulk_delete_by_ids(target_ids)
# 返回结果
return deleted_count
# 3. Repositories 层 (repositories/django_target_repository.py)
class DjangoTargetRepository:
def bulk_delete_by_ids(self, target_ids):
# 数据访问操作
return Target.objects.filter(id__in=target_ids).delete()
# 4. Models 层 (models.py)
class Target(models.Model):
# ORM 定义
name = models.CharField(...)
```
---
### **场景 2异步删除Views → Tasks → Services → Repositories → Models**
```python
# 1. Views 层 (views.py)
def destroy(self, request, *args, **kwargs):
target = self.get_object()
# 调用 Tasks 层(异步)
async_bulk_delete_targets([target.id], [target.name])
# 立即返回 202
return Response(status=202)
# 2. Tasks 层 (tasks/target_tasks.py)
def async_bulk_delete_targets(target_ids, target_names):
def _delete():
# 发送通知
create_notification("删除中...")
# 调用 Service 层
service = TargetService()
result = service.bulk_delete_targets(target_ids)
# 发送完成通知
create_notification("删除成功")
# 后台线程执行
threading.Thread(target=_delete).start()
# 3. Services 层 (services/target_service.py)
class TargetService:
def bulk_delete_targets(self, target_ids):
# 业务逻辑
return self.repo.bulk_delete_by_ids(target_ids)
# 4. Repositories 层 (repositories/django_target_repository.py)
class DjangoTargetRepository:
def bulk_delete_by_ids(self, target_ids):
# 数据访问
return Target.objects.filter(id__in=target_ids).delete()
# 5. Models 层 (models.py)
class Target(models.Model):
# ORM 定义
...
```
---
### 📋 各层职责清单
| 层级 | 职责 | 不应该做 |
| --- | --- | --- |
| **Views** | HTTP 请求处理、参数验证、权限检查 | ❌ 直接访问 Models<br>❌ 业务逻辑 |
| **Tasks** | 异步执行、后台作业、通知发送 | ❌ 直接访问 Models<br>❌ HTTP 响应 |
| **Services** | 业务逻辑、事务管理、数据验证 | ❌ 直接写 SQL<br>❌ HTTP 相关 |
| **Repositories** | 数据访问、查询封装、批量操作 | ❌ 业务逻辑<br>❌ 通知发送 |
| **Models** | ORM 定义、数据结构、关系映射 | ❌ 业务逻辑<br>❌ 复杂查询 |
---
### ✅ 最佳实践原则
1. **单向依赖**:只能向下调用,不能向上调用
```
Views → Tasks → Services → Repositories → Models
(上层) (下层)
```
2. **层级隔离**:相邻层交互,禁止跨层
- ✅ Views → Services
- ✅ Tasks → Services
- ✅ Services → Repositories
- ❌ Views → Repositories跨层
- ❌ Tasks → Models跨层
3. **依赖注入**:通过构造函数注入依赖
```python
class TargetService:
def __init__(self):
self.repo = DjangoTargetRepository() # 注入
```
4. **接口抽象**:使用 Protocol 定义接口
```python
class TargetRepository(Protocol):
def bulk_delete_by_ids(self, ids): ...
```

View File

@@ -1 +1 @@
v1.0.7
v1.0.10

View File

@@ -8,13 +8,32 @@
2. 选择负载最低的 Worker可能是本地或远程
3. 本地 Worker直接执行 docker run
4. 远程 Worker通过 SSH 执行 docker run
5. 任务执行完自动销毁容器
5. 任务执行完自动销毁容器--rm
镜像版本管理:
- 版本锁定:使用 settings.IMAGE_TAG 确保 server 和 worker 版本一致
- 预拉取策略:安装时预拉取镜像,执行时使用 --pull=missing
- 本地开发:可通过 TASK_EXECUTOR_IMAGE 环境变量指向本地镜像
环境变量注入:
- Worker 容器不使用 env_file通过 docker run -e 动态注入
- 只注入 SERVER_URL容器启动后从配置中心获取完整配置
- 本地 WorkerSERVER_URL = http://server:{port}Docker 内部网络)
- 远程 WorkerSERVER_URL = http://{public_host}:{port}(公网地址)
任务启动流程:
1. Server 调用 execute_scan_flow() 等方法提交任务
2. select_best_worker() 从 Redis 读取心跳数据,选择负载最低的节点
3. _build_docker_command() 构建完整的 docker run 命令:
- 设置网络(本地加入 Docker 网络,远程不指定)
- 注入环境变量(-e SERVER_URL=...
- 挂载结果和日志目录(-v
- 指定执行脚本python -m apps.scan.scripts.xxx
4. _execute_docker_command() 执行命令:
- 本地subprocess.run() 直接执行
- 远程paramiko SSH 执行
5. docker run -d 立即返回容器 ID任务在后台执行
特点:
- 负载感知:任务优先分发到最空闲的机器
- 统一调度:本地和远程 Worker 使用相同的选择逻辑
@@ -203,7 +222,12 @@ class TaskDistributor:
host_logs_dir = settings.HOST_LOGS_DIR # /opt/xingrin/logs
# 环境变量:只需 SERVER_URL其他配置容器启动时从配置中心获取
env_vars = [f"-e SERVER_URL={shlex.quote(server_url)}"]
# Prefect 本地模式配置:禁用 API server 和事件系统
env_vars = [
f"-e SERVER_URL={shlex.quote(server_url)}",
"-e PREFECT_API_URL=", # 禁用 API server
"-e PREFECT_LOGGING_EXTRA_LOGGERS=", # 禁用 Prefect 的额外内部日志器
]
# 挂载卷
volumes = [

View File

@@ -157,6 +157,51 @@ class ScanService:
"""取消所有正在运行的阶段(委托给 ScanStateService"""
return self.state_service.cancel_running_stages(scan_id, final_status)
# todo待接入
def add_command_to_scan(self, scan_id: int, stage_name: str, tool_name: str, command: str) -> bool:
"""
增量添加命令到指定扫描阶段
Args:
scan_id: 扫描任务ID
stage_name: 阶段名称(如 'subdomain_discovery', 'port_scan'
tool_name: 工具名称
command: 执行命令
Returns:
bool: 是否成功添加
"""
try:
scan = self.get_scan(scan_id, prefetch_relations=False)
if not scan:
logger.error(f"扫描任务不存在: {scan_id}")
return False
stage_progress = scan.stage_progress or {}
# 确保指定阶段存在
if stage_name not in stage_progress:
stage_progress[stage_name] = {'status': 'running', 'commands': []}
# 确保 commands 列表存在
if 'commands' not in stage_progress[stage_name]:
stage_progress[stage_name]['commands'] = []
# 增量添加命令
command_entry = f"{tool_name}: {command}"
stage_progress[stage_name]['commands'].append(command_entry)
scan.stage_progress = stage_progress
scan.save(update_fields=['stage_progress'])
command_count = len(stage_progress[stage_name]['commands'])
logger.info(f"✓ 记录命令: {stage_name}.{tool_name} (总计: {command_count})")
return True
except Exception as e:
logger.error(f"记录命令失败: {e}")
return False
# ==================== 删除和控制方法(委托给 ScanControlService ====================
def delete_scans_two_phase(self, scan_ids: List[int]) -> dict:

View File

@@ -225,6 +225,13 @@ def _parse_and_validate_line(line: str) -> Optional[PortScanRecord]:
ip = line_data.get('ip', '').strip()
port = line_data.get('port')
logger.debug("解析到的主机名: %s, IP: %s, 端口: %s", host, ip, port)
if not host and ip:
host = ip
logger.debug("主机名为空,使用 IP 作为 host")
# 步骤 4: 验证字段不为空
if not host or not ip or port is None:
logger.warning(

View File

@@ -1,4 +1,8 @@
#!/bin/bash
# 目前采用github action自动版本构建
# git tag v1.0.9
# git push origin v1.0.9
# ============================================
# Docker Hub 镜像推送脚本
# 用途:构建并推送所有服务镜像到 Docker Hub

View File

@@ -101,6 +101,18 @@ services:
# SSL 证书挂载(方便更新)
- ./nginx/ssl:/etc/nginx/ssl:ro
# Worker扫描任务执行容器开发模式下构建
worker:
build:
context: ..
dockerfile: docker/worker/Dockerfile
image: docker-worker:${IMAGE_TAG:-latest}-dev
restart: "no"
volumes:
- /opt/xingrin/results:/app/backend/results
- /opt/xingrin/logs:/app/backend/logs
command: echo "Worker image built for development"
volumes:
postgres_data:

View File

@@ -27,10 +27,10 @@ check_docker() {
# ==================== Docker Compose 命令检测 ====================
detect_compose_cmd() {
if command -v docker-compose >/dev/null 2>&1; then
COMPOSE_CMD="docker-compose"
elif docker compose version >/dev/null 2>&1; then
if docker compose version >/dev/null 2>&1; then
COMPOSE_CMD="docker compose"
elif command -v docker-compose >/dev/null 2>&1; then
COMPOSE_CMD="docker-compose"
else
log_error "未检测到 docker-compose 或 docker compose。"
exit 1

View File

@@ -42,10 +42,10 @@ if ! docker info >/dev/null 2>&1; then
exit 1
fi
if command -v docker-compose >/dev/null 2>&1; then
COMPOSE_CMD="docker-compose"
elif docker compose version >/dev/null 2>&1; then
if docker compose version >/dev/null 2>&1; then
COMPOSE_CMD="docker compose"
elif command -v docker-compose >/dev/null 2>&1; then
COMPOSE_CMD="docker-compose"
else
echo -e "${RED}[ERROR]${NC} 未检测到 docker compose请先安装"
exit 1

View File

@@ -79,10 +79,10 @@ ENV GOPATH=/root/go
ENV PATH=/usr/local/go/bin:$PATH:$GOPATH/bin
ENV GOPROXY=https://goproxy.cn,direct
# 5. 安装 uv超快的 Python 包管理器)
# 5. 安装 uv Python 包管理器)
RUN pip install uv --break-system-packages
# 安装 Python 依赖(使用 uv 并行下载,速度快 10-100 倍
# 安装 Python 依赖(使用 uv 并行下载)
COPY backend/requirements.txt .
RUN --mount=type=cache,target=/root/.cache/uv \
uv pip install --system -r requirements.txt --break-system-packages && \

View File

@@ -152,13 +152,13 @@ sequenceDiagram
### 本地开发测试
```bash
# docker/.env 中添加
TASK_EXECUTOR_IMAGE=docker-agent:latest # 指向本地构建镜像
# docker/.env 中添加(开发模式会自动设置)
TASK_EXECUTOR_IMAGE=docker-worker:v1.1.0-dev # 指向本地构建镜像
```
### 开发模式启动
```bash
# 使用本地构建镜像
# 使用本地构建镜像(自动构建并标记为 ${VERSION}-dev
./install.sh --dev
./start.sh --dev
```
@@ -238,7 +238,8 @@ curl -s https://hub.docker.com/v2/repositories/yyhuni/xingrin-worker/tags/
4. ✅ 使用 `docker system prune` 清理旧镜像
### 开发调试
1. ✅ 本地测试使用 `--dev` 模式
1. ✅ 本地测试使用 `--dev` 模式(自动构建 `docker-worker:${VERSION}-dev`
2. ✅ 远程测试先推送测试版本到 Hub
3. ✅ 生产环境避免使用 `latest` 标签
4. ✅ 版本回滚通过修改 `IMAGE_TAG` 实现
3. ✅ 生产环境避免使用 `latest` 标签,始终使用明确版本号
4.开发环境使用 `-dev` 后缀区分开发版本
5. ✅ 版本回滚通过修改 `IMAGE_TAG` 实现

View File

@@ -78,21 +78,21 @@ export function createIPAddressColumns(params: {
enableSorting: false,
enableHiding: false,
},
// IP 地址
// IP 列
{
accessorKey: "ip",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="IP 地址" />
<DataTableColumnHeader column={column} title="IP Address" />
),
cell: ({ row }) => (
<TruncatedCell value={row.original.ip} maxLength="ip" mono />
),
},
// 关联主机名
// host
{
accessorKey: "hosts",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="关联主机" />
<DataTableColumnHeader column={column} title="Hosts" />
),
cell: ({ getValue }) => {
const hosts = getValue<string[]>()
@@ -107,7 +107,7 @@ export function createIPAddressColumns(params: {
return (
<div className="flex flex-col gap-1">
{displayHosts.map((host, index) => (
<span key={index} className="text-sm font-mono">{host}</span>
<TruncatedCell key={index} value={host} maxLength="host" mono />
))}
{hasMore && (
<Badge variant="secondary" className="text-xs w-fit">
@@ -118,11 +118,11 @@ export function createIPAddressColumns(params: {
)
},
},
// 发现时间
// discoveredAt
{
accessorKey: "discoveredAt",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="发现时间" />
<DataTableColumnHeader column={column} title="Discovered At" />
),
cell: ({ getValue }) => {
const value = getValue<string | undefined>()
@@ -133,7 +133,7 @@ export function createIPAddressColumns(params: {
{
accessorKey: "ports",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="开放端口" />
<DataTableColumnHeader column={column} title="Open Ports" />
),
cell: ({ getValue }) => {
const ports = getValue<number[]>()
@@ -191,7 +191,7 @@ export function createIPAddressColumns(params: {
</PopoverTrigger>
<PopoverContent className="w-80 p-3">
<div className="space-y-2">
<h4 className="font-medium text-sm"> ({sortedPorts.length})</h4>
<h4 className="font-medium text-sm">All Open Ports ({sortedPorts.length})</h4>
<div className="flex flex-wrap gap-1 max-h-32 overflow-y-auto">
{sortedPorts.map((port, index) => (
<Badge

View File

@@ -267,7 +267,7 @@ export const createTargetColumns = ({
{
accessorKey: "name",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="目标名称" />
<DataTableColumnHeader column={column} title="Target Name" />
),
cell: ({ row }) => (
<TargetNameCell
@@ -282,7 +282,7 @@ export const createTargetColumns = ({
{
accessorKey: "type",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="类型" />
<DataTableColumnHeader column={column} title="Type" />
),
cell: ({ row }) => {
const type = row.getValue("type") as string | null

View File

@@ -188,7 +188,7 @@ export const createEngineColumns = ({
{
accessorKey: "name",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="引擎名称" />
<DataTableColumnHeader column={column} title="Engine Name" />
),
cell: ({ row }) => {
const name = row.getValue("name") as string

View File

@@ -180,7 +180,7 @@ export const createScheduledScanColumns = ({
{
accessorKey: "name",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="任务名称" />
<DataTableColumnHeader column={column} title="Task Name" />
),
cell: ({ row }) => {
const name = row.getValue("name") as string
@@ -216,7 +216,7 @@ export const createScheduledScanColumns = ({
{
accessorKey: "engineName",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="扫描引擎" />
<DataTableColumnHeader column={column} title="Scan Engine" />
),
cell: ({ row }) => {
const engineName = row.getValue("engineName") as string
@@ -283,7 +283,7 @@ export const createScheduledScanColumns = ({
{
accessorKey: "isEnabled",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="状态" />
<DataTableColumnHeader column={column} title="Status" />
),
cell: ({ row }) => {
const isEnabled = row.getValue("isEnabled") as boolean
@@ -308,7 +308,7 @@ export const createScheduledScanColumns = ({
{
accessorKey: "nextRunTime",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="下次执行" />
<DataTableColumnHeader column={column} title="Next Run" />
),
cell: ({ row }) => {
const nextRunTime = row.getValue("nextRunTime") as string | undefined
@@ -324,7 +324,7 @@ export const createScheduledScanColumns = ({
{
accessorKey: "runCount",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="执行次数" />
<DataTableColumnHeader column={column} title="Run Count" />
),
cell: ({ row }) => {
const count = row.getValue("runCount") as number
@@ -338,7 +338,7 @@ export const createScheduledScanColumns = ({
{
accessorKey: "lastRunTime",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="上次执行" />
<DataTableColumnHeader column={column} title="Last Run" />
),
cell: ({ row }) => {
const lastRunTime = row.getValue("lastRunTime") as string | undefined

View File

@@ -100,7 +100,7 @@ export const createSubdomainColumns = ({
{
accessorKey: "discoveredAt",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="发现时间" />
<DataTableColumnHeader column={column} title="Discovered At" />
),
cell: ({ getValue }) => {
const value = getValue<string | undefined>()

View File

@@ -95,7 +95,7 @@ export const commandColumns: ColumnDef<Command>[] = [
{
accessorKey: "displayName",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="名称" />
<DataTableColumnHeader column={column} title="Name" />
),
cell: ({ row }) => {
const displayName = row.getValue("displayName") as string
@@ -136,7 +136,7 @@ export const commandColumns: ColumnDef<Command>[] = [
{
accessorKey: "tool",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="所属工具" />
<DataTableColumnHeader column={column} title="Tool" />
),
cell: ({ row }) => {
const tool = row.original.tool
@@ -156,7 +156,7 @@ export const commandColumns: ColumnDef<Command>[] = [
{
accessorKey: "commandTemplate",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="命令模板" />
<DataTableColumnHeader column={column} title="Command Template" />
),
cell: ({ row }) => {
const template = row.getValue("commandTemplate") as string
@@ -192,7 +192,7 @@ export const commandColumns: ColumnDef<Command>[] = [
{
accessorKey: "description",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="描述" />
<DataTableColumnHeader column={column} title="Description" />
),
cell: ({ row }) => {
const description = row.getValue("description") as string
@@ -217,7 +217,7 @@ export const commandColumns: ColumnDef<Command>[] = [
{
accessorKey: "updatedAt",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="更新时间" />
<DataTableColumnHeader column={column} title="Updated At" />
),
cell: ({ row }) => (
<div className="text-sm text-muted-foreground">

View File

@@ -126,7 +126,7 @@ update_env_var() {
GENERATED_DB_PASSWORD=""
GENERATED_DJANGO_KEY=""
# 生成自签 HTTPS 证书(无域名场景
# 生成自签 HTTPS 证书(使用容器,避免宿主机 openssl 兼容性问题
generate_self_signed_cert() {
local ssl_dir="$DOCKER_DIR/nginx/ssl"
local fullchain="$ssl_dir/fullchain.pem"
@@ -139,14 +139,18 @@ generate_self_signed_cert() {
info "未检测到 HTTPS 证书正在生成自签证书localhost..."
mkdir -p "$ssl_dir"
if openssl req -x509 -nodes -newkey rsa:2048 -days 365 \
-keyout "$privkey" \
-out "$fullchain" \
# 使用容器生成证书,避免依赖宿主机 openssl 版本
if docker run --rm -v "$ssl_dir:/ssl" alpine/openssl \
req -x509 -nodes -newkey rsa:2048 -days 365 \
-keyout /ssl/privkey.pem \
-out /ssl/fullchain.pem \
-subj "/C=CN/ST=NA/L=NA/O=XingRin/CN=localhost" \
-addext "subjectAltName=DNS:localhost,IP:127.0.0.1" >/dev/null 2>&1; then
-addext "subjectAltName=DNS:localhost,IP:127.0.0.1" \
>/dev/null 2>&1; then
success "自签证书已生成: $ssl_dir"
else
warn "自签证书生成失败,请检查 openssl 是否可用,或手动放置证书到 $ssl_dir"
warn "自签证书生成失败,请手动放置证书到 $ssl_dir"
fi
}
@@ -225,7 +229,7 @@ show_summary() {
step "[1/3] 检查基础命令"
MISSING_CMDS=()
for cmd in git curl jq openssl; do
for cmd in git curl; do
if ! command -v "$cmd" >/dev/null 2>&1; then
MISSING_CMDS+=("$cmd")
warn "未安装: $cmd"
@@ -396,11 +400,28 @@ DOCKER_USER=$(grep "^DOCKER_USER=" "$DOCKER_DIR/.env" 2>/dev/null | cut -d= -f2)
DOCKER_USER=${DOCKER_USER:-yyhuni}
WORKER_IMAGE="${DOCKER_USER}/xingrin-worker:${APP_VERSION}"
info "正在拉取: $WORKER_IMAGE"
if docker pull "$WORKER_IMAGE"; then
success "Worker 镜像拉取完成"
# 开发模式下构建本地 worker 镜像
if [ "$DEV_MODE" = true ]; then
info "开发模式:构建本地 Worker 镜像..."
if docker compose -f "$DOCKER_DIR/docker-compose.dev.yml" build worker; then
# 设置 TASK_EXECUTOR_IMAGE 环境变量指向本地构建的镜像(使用版本号-dev标识
update_env_var "$DOCKER_DIR/.env" "TASK_EXECUTOR_IMAGE" "docker-worker:${APP_VERSION}-dev"
success "本地 Worker 镜像构建完成: docker-worker:${APP_VERSION}-dev"
else
error "开发模式下本地 Worker 镜像构建失败!"
error "请检查构建错误并修复后重试"
exit 1
fi
else
warn "Worker 镜像拉取失败,扫描时会自动重试拉取"
info "正在拉取: $WORKER_IMAGE"
if docker pull "$WORKER_IMAGE"; then
success "Worker 镜像拉取完成"
else
error "Worker 镜像拉取失败,无法继续安装"
error "请检查网络连接或 Docker Hub 访问权限"
error "镜像地址: $WORKER_IMAGE"
exit 1
fi
fi
# ==============================================================================