chore: remove all Chinese text, full English internationalization for gitea-webhook-ambassador-python

This commit is contained in:
Nicolas 2025-07-21 15:33:35 +08:00
parent 5b93048cb3
commit d03c119322
24 changed files with 857 additions and 405 deletions

View File

@ -11,10 +11,10 @@ from typing import Optional
from ..models.database import get_db, APIKey
from ..config import settings
# JWT 配置
# JWT configuration
JWT_SECRET_KEY = os.getenv("JWT_SECRET_KEY", "your-secret-key-change-in-production")
JWT_ALGORITHM = "HS256"
JWT_EXPIRATION_HOURS = 24 * 7 # 7 天有效期
JWT_EXPIRATION_HOURS = 24 * 7 # 7 days expiration
security = HTTPBearer()
@ -49,25 +49,25 @@ class AuthMiddleware:
)
def verify_api_key(self, api_key: str, db: Session):
"""验证 API 密钥"""
"""Validate API key"""
db_key = db.query(APIKey).filter(APIKey.key == api_key).first()
return db_key is not None
def generate_api_key(self) -> str:
"""生成新的 API 密钥"""
"""Generate a new API key"""
return secrets.token_urlsafe(32)
# 创建认证中间件实例
# Create authentication middleware instance
auth_middleware = AuthMiddleware()
async def get_current_user(credentials: HTTPAuthorizationCredentials = Depends(security)):
"""获取当前用户JWT 认证)"""
"""Get current user (JWT authentication)"""
token = credentials.credentials
payload = auth_middleware.verify_token(token)
return payload
async def get_current_user_api_key(api_key: str = Depends(security), db: Session = Depends(get_db)):
"""获取当前用户API 密钥认证)"""
"""Get current user (API key authentication)"""
if not auth_middleware.verify_api_key(api_key.credentials, db):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
@ -76,14 +76,14 @@ async def get_current_user_api_key(api_key: str = Depends(security), db: Session
return {"api_key": api_key.credentials}
def require_auth(use_api_key: bool = False):
"""认证依赖装饰器"""
"""Authentication dependency decorator"""
if use_api_key:
return get_current_user_api_key
else:
return get_current_user
def handle_auth_error(request, exc):
"""处理认证错误"""
"""Handle authentication error"""
if request.headers.get("x-requested-with") == "XMLHttpRequest":
return JSONResponse(
status_code=401,

View File

@ -10,7 +10,7 @@ from ..auth.middleware import auth_middleware
router = APIRouter(prefix="/api/auth", tags=["authentication"])
# 请求/响应模型
# Request/Response models
class LoginRequest(BaseModel):
secret_key: str
@ -32,13 +32,13 @@ class APIKeyResponse(BaseModel):
class APIKeyList(BaseModel):
keys: List[APIKeyResponse]
# 获取管理员密钥
# Get admin secret key
def get_admin_secret_key():
return os.getenv("ADMIN_SECRET_KEY", "admin-secret-key-change-in-production")
@router.post("/login", response_model=LoginResponse)
async def login(request: LoginRequest):
"""管理员登录"""
"""Admin login"""
admin_key = get_admin_secret_key()
if request.secret_key != admin_key:
@ -47,7 +47,7 @@ async def login(request: LoginRequest):
detail="Invalid secret key"
)
# 生成 JWT 令牌
# Generate JWT token
token = auth_middleware.create_access_token(
data={"sub": "admin", "role": "admin"}
)
@ -60,11 +60,11 @@ async def create_api_key(
db: Session = Depends(get_db),
current_user: dict = Depends(auth_middleware.get_current_user)
):
"""创建新的 API 密钥"""
# 生成新的 API 密钥
"""Create a new API key"""
# Generate new API key
api_key_value = auth_middleware.generate_api_key()
# 保存到数据库
# Save to database
db_key = APIKey(
key=api_key_value,
description=request.description
@ -86,7 +86,7 @@ async def list_api_keys(
db: Session = Depends(get_db),
current_user: dict = Depends(auth_middleware.get_current_user)
):
"""获取所有 API 密钥"""
"""Get all API keys"""
keys = db.query(APIKey).order_by(APIKey.created_at.desc()).all()
return APIKeyList(
@ -107,7 +107,7 @@ async def delete_api_key(
db: Session = Depends(get_db),
current_user: dict = Depends(auth_middleware.get_current_user)
):
"""删除 API 密钥"""
"""Delete API key"""
key = db.query(APIKey).filter(APIKey.id == key_id).first()
if not key:

View File

@ -1,6 +1,6 @@
"""
健康检查处理器
提供服务健康状态检查
Health check handler
Provides service health status checking
"""
from datetime import datetime
@ -42,12 +42,12 @@ class HealthResponse(BaseModel):
@router.get("/", response_model=HealthResponse)
async def health_check(db: Session = Depends(get_db)):
"""
健康检查端点
检查服务各个组件的状态
Health check endpoint
Check the status of each service component
"""
settings = get_settings()
# 检查 Jenkins 连接
# Check Jenkins connection
jenkins_service = get_jenkins_service()
jenkins_status = JenkinsStatus(status="disconnected", message="Unable to connect to Jenkins server")
@ -57,7 +57,7 @@ async def health_check(db: Session = Depends(get_db)):
except Exception as e:
jenkins_status.message = f"Connection failed: {str(e)}"
# 获取工作池统计
# Get worker pool stats
queue_service = get_queue_service()
try:
stats = await queue_service.get_stats()
@ -75,7 +75,7 @@ async def health_check(db: Session = Depends(get_db)):
total_failed=0
)
# 检查数据库连接
# Check database connection
database_status = {"status": "disconnected", "message": "Database connection failed"}
try:
# 尝试执行简单查询
@ -84,7 +84,7 @@ async def health_check(db: Session = Depends(get_db)):
except Exception as e:
database_status["message"] = f"Database error: {str(e)}"
# 确定整体状态
# Determine overall status
overall_status = "healthy"
if jenkins_status.status != "connected":
overall_status = "unhealthy"
@ -103,8 +103,8 @@ async def health_check(db: Session = Depends(get_db)):
@router.get("/simple")
async def simple_health_check():
"""
简单健康检查端点
用于负载均衡器和监控系统
Simple health check endpoint
For load balancers and monitoring systems
"""
return {
"status": "healthy",
@ -116,14 +116,14 @@ async def simple_health_check():
@router.get("/ready")
async def readiness_check(db: Session = Depends(get_db)):
"""
就绪检查端点
检查服务是否准备好接收请求
Readiness check endpoint
Check if the service is ready to receive requests
"""
try:
# 检查数据库连接
# Check database connection
db.execute("SELECT 1")
# 检查 Jenkins 连接
# Check Jenkins connection
jenkins_service = get_jenkins_service()
jenkins_ready = await jenkins_service.test_connection()
@ -139,7 +139,7 @@ async def readiness_check(db: Session = Depends(get_db)):
@router.get("/live")
async def liveness_check():
"""
存活检查端点
检查服务进程是否正常运行
Liveness check endpoint
Check if the service process is running normally
"""
return {"status": "alive"}

View File

@ -35,13 +35,13 @@ async def get_trigger_logs(
current_user: dict = Depends(get_current_user)
):
"""
获取触发日志
Get trigger logs
"""
try:
# 构建查询
# Build query
query = db.query(TriggerLog)
# 应用过滤器
# Apply filters
if repository:
query = query.filter(TriggerLog.repository_name == repository)
if branch:
@ -56,7 +56,7 @@ async def get_trigger_logs(
detail="Invalid since parameter format (use RFC3339)"
)
# 按时间倒序排列并限制数量
# Order by time desc and limit
logs = query.order_by(TriggerLog.created_at.desc()).limit(limit).all()
return logs
@ -71,21 +71,21 @@ async def get_log_stats(
current_user: dict = Depends(get_current_user)
):
"""
获取日志统计信息
Get log statistics
"""
try:
# 总日志数
# Total logs
total_logs = db.query(TriggerLog).count()
# 成功和失败的日志数
# Successful and failed logs
successful_logs = db.query(TriggerLog).filter(TriggerLog.status == "success").count()
failed_logs = db.query(TriggerLog).filter(TriggerLog.status == "failed").count()
# 最近24小时的日志数
# Logs in the last 24 hours
yesterday = datetime.utcnow() - timedelta(days=1)
recent_logs = db.query(TriggerLog).filter(TriggerLog.created_at >= yesterday).count()
# 按仓库分组的统计
# Stats by repository
repo_stats = db.query(
TriggerLog.repository_name,
db.func.count(TriggerLog.id).label('count')

View File

@ -8,7 +8,7 @@ from ..auth.middleware import auth_middleware
router = APIRouter(prefix="/api/projects", tags=["projects"])
# 请求/响应模型
# Request/Response models
class ProjectCreate(BaseModel):
name: str
jenkinsJob: str
@ -33,8 +33,8 @@ async def create_project(
db: Session = Depends(get_db),
current_user: dict = Depends(auth_middleware.get_current_user)
):
"""创建新项目映射"""
# 检查项目是否已存在
"""Create new project mapping"""
# Check if project already exists
existing_project = db.query(ProjectMapping).filter(
ProjectMapping.repository_name == request.giteaRepo
).first()
@ -45,7 +45,7 @@ async def create_project(
detail="Project with this repository already exists"
)
# 创建新项目
# Create new project
project = ProjectMapping(
repository_name=request.giteaRepo,
default_job=request.jenkinsJob
@ -68,14 +68,14 @@ async def list_projects(
db: Session = Depends(get_db),
current_user: dict = Depends(auth_middleware.get_current_user)
):
"""获取所有项目"""
"""Get all projects"""
projects = db.query(ProjectMapping).order_by(ProjectMapping.created_at.desc()).all()
return ProjectList(
projects=[
ProjectResponse(
id=project.id,
name=project.repository_name.split('/')[-1], # 使用仓库名作为项目名
name=project.repository_name.split('/')[-1], # Use repo name as project name
jenkinsJob=project.default_job,
giteaRepo=project.repository_name,
created_at=project.created_at.isoformat()
@ -90,7 +90,7 @@ async def get_project(
db: Session = Depends(get_db),
current_user: dict = Depends(auth_middleware.get_current_user)
):
"""获取特定项目"""
"""Get specific project"""
project = db.query(ProjectMapping).filter(ProjectMapping.id == project_id).first()
if not project:
@ -113,7 +113,7 @@ async def delete_project(
db: Session = Depends(get_db),
current_user: dict = Depends(auth_middleware.get_current_user)
):
"""删除项目"""
"""Delete project"""
project = db.query(ProjectMapping).filter(ProjectMapping.id == project_id).first()
if not project:
@ -132,7 +132,7 @@ async def get_project_mapping(
repository_name: str,
db: Session = Depends(get_db)
):
"""根据仓库名获取项目映射(用于 webhook 处理)"""
"""Get project mapping by repository name (for webhook processing)"""
project = db.query(ProjectMapping).filter(
ProjectMapping.repository_name == repository_name
).first()

View File

@ -1,6 +1,6 @@
"""
FastAPI 应用主入口
集成 Webhook 处理防抖队列管理等服务
Main entry for FastAPI application
Integrates webhook handling, deduplication, queue management, and related services
"""
import asyncio
@ -18,9 +18,9 @@ from app.services.dedup_service import DeduplicationService
from app.services.jenkins_service import JenkinsService
from app.services.webhook_service import WebhookService
from app.tasks.jenkins_tasks import get_celery_app
# 路由导入将在运行时动态处理
# Route imports will be dynamically handled at runtime
# 配置结构化日志
# Configure structured logging
structlog.configure(
processors=[
structlog.stdlib.filter_by_level,
@ -41,7 +41,7 @@ structlog.configure(
logger = structlog.get_logger()
# 监控指标
# Monitoring metrics
WEBHOOK_REQUESTS_TOTAL = Counter(
"webhook_requests_total",
"Total number of webhook requests",
@ -65,7 +65,7 @@ DEDUP_HITS = Counter(
"Total number of deduplication hits"
)
# 全局服务实例
# Global service instances
dedup_service: DeduplicationService = None
jenkins_service: JenkinsService = None
webhook_service: WebhookService = None
@ -75,14 +75,14 @@ redis_client: aioredis.Redis = None
@asynccontextmanager
async def lifespan(app: FastAPI):
"""应用生命周期管理"""
"""Application lifecycle management"""
global dedup_service, jenkins_service, webhook_service, celery_app, redis_client
# 启动时初始化
# Initialize on startup
logger.info("Starting Gitea Webhook Ambassador")
try:
# 初始化 Redis 连接
# Initialize Redis connection
settings = get_settings()
redis_client = aioredis.from_url(
settings.redis.url,
@ -92,14 +92,14 @@ async def lifespan(app: FastAPI):
decode_responses=True
)
# 测试 Redis 连接
# Test Redis connection
await redis_client.ping()
logger.info("Redis connection established")
# 初始化 Celery
# Initialize Celery
celery_app = get_celery_app()
# 初始化服务
# Initialize services
dedup_service = DeduplicationService(redis_client)
jenkins_service = JenkinsService()
webhook_service = WebhookService(
@ -117,7 +117,7 @@ async def lifespan(app: FastAPI):
raise
finally:
# 关闭时清理
# Cleanup on shutdown
logger.info("Shutting down Gitea Webhook Ambassador")
if redis_client:
@ -125,25 +125,25 @@ async def lifespan(app: FastAPI):
logger.info("Redis connection closed")
# 创建 FastAPI 应用
# Create FastAPI application
app = FastAPI(
title="Gitea Webhook Ambassador",
description="高性能的 Gitea 到 Jenkins 的 Webhook 服务",
description="High-performance Gitea to Jenkins Webhook service",
version="1.0.0",
lifespan=lifespan
)
# 添加 CORS 中间件
# Add CORS middleware
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # 生产环境应该限制具体域名
allow_origins=["*"], # In production, restrict to specific domains
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# 依赖注入
# Dependency injection
def get_dedup_service() -> DeduplicationService:
if dedup_service is None:
raise HTTPException(status_code=503, detail="Deduplication service not available")
@ -162,13 +162,13 @@ def get_celery_app_dep():
return celery_app
# 中间件
# Middleware
@app.middleware("http")
async def log_requests(request: Request, call_next):
"""请求日志中间件"""
"""Request logging middleware"""
start_time = asyncio.get_event_loop().time()
# 记录请求开始
# Log request start
logger.info("Request started",
method=request.method,
url=str(request.url),
@ -177,7 +177,7 @@ async def log_requests(request: Request, call_next):
try:
response = await call_next(request)
# 记录请求完成
# Log request complete
process_time = asyncio.get_event_loop().time() - start_time
logger.info("Request completed",
method=request.method,
@ -188,7 +188,7 @@ async def log_requests(request: Request, call_next):
return response
except Exception as e:
# 记录请求错误
# Log request error
process_time = asyncio.get_event_loop().time() - start_time
logger.error("Request failed",
method=request.method,
@ -200,10 +200,10 @@ async def log_requests(request: Request, call_next):
@app.middleware("http")
async def add_security_headers(request: Request, call_next):
"""添加安全头"""
"""Add security headers"""
response = await call_next(request)
# 添加安全相关的 HTTP 头
# Add security-related HTTP headers
response.headers["X-Content-Type-Options"] = "nosniff"
response.headers["X-Frame-Options"] = "DENY"
response.headers["X-XSS-Protection"] = "1; mode=block"
@ -212,10 +212,10 @@ async def add_security_headers(request: Request, call_next):
return response
# 异常处理器
# Exception handler
@app.exception_handler(Exception)
async def global_exception_handler(request: Request, exc: Exception):
"""全局异常处理器"""
"""Global exception handler"""
logger.error("Unhandled exception",
method=request.method,
url=str(request.url),
@ -232,19 +232,19 @@ async def global_exception_handler(request: Request, exc: Exception):
)
# 健康检查端点
# Health check endpoint
@app.get("/health")
async def health_check():
"""基础健康检查"""
"""Basic health check"""
try:
# 检查 Redis 连接
# Check Redis connection
if redis_client:
await redis_client.ping()
redis_healthy = True
else:
redis_healthy = False
# 检查 Celery 连接
# Check Celery connection
if celery_app:
inspect = celery_app.control.inspect()
celery_healthy = bool(inspect.active() is not None)
@ -273,7 +273,7 @@ async def health_check():
@app.get("/health/queue")
async def queue_health_check():
"""队列健康检查"""
"""Queue health check"""
try:
if celery_app is None:
return JSONResponse(
@ -283,7 +283,7 @@ async def queue_health_check():
inspect = celery_app.control.inspect()
# 获取队列统计
# Get queue stats
active = inspect.active()
reserved = inspect.reserved()
registered = inspect.registered()
@ -292,7 +292,7 @@ async def queue_health_check():
reserved_count = sum(len(tasks) for tasks in (reserved or {}).values())
worker_count = len(registered or {})
# 更新监控指标
# Update monitoring metrics
QUEUE_SIZE.labels(queue_type="active").set(active_count)
QUEUE_SIZE.labels(queue_type="reserved").set(reserved_count)
@ -317,17 +317,17 @@ async def queue_health_check():
)
# 监控指标端点
# Metrics endpoint
@app.get("/metrics")
async def metrics():
"""Prometheus 监控指标"""
"""Prometheus metrics endpoint"""
return Response(
content=generate_latest(),
media_type=CONTENT_TYPE_LATEST
)
# 包含路由模块
# Include route modules
try:
from app.handlers import webhook, health, admin
@ -349,18 +349,17 @@ try:
tags=["admin"]
)
except ImportError as e:
# 如果模块不存在,记录警告但不中断应用启动
# If module does not exist, log warning but do not interrupt app startup
logger.warning(f"Some handlers not available: {e}")
# 根路径
# Root path
@app.get("/")
async def root():
"""根路径"""
"""Root path"""
return {
"name": "Gitea Webhook Ambassador",
"version": "1.0.0",
"description": "高性能的 Gitea 到 Jenkins 的 Webhook 服务",
"description": "High-performance Gitea to Jenkins Webhook service",
"endpoints": {
"webhook": "/webhook/gitea",
"health": "/health",

View File

@ -9,19 +9,19 @@ import time
import psutil
from datetime import datetime, timedelta
# 导入数据库模型
# Import database models
from app.models.database import create_tables, get_db, APIKey, ProjectMapping, TriggerLog
from app.auth.middleware import auth_middleware, get_current_user
from app.config import settings
# 创建 FastAPI 应用
# Create FastAPI app
app = FastAPI(
title="Gitea Webhook Ambassador",
description="高性能的 Gitea 到 Jenkins 的 Webhook 服务",
description="High-performance Gitea to Jenkins Webhook service",
version="2.0.0"
)
# 添加 CORS 中间件
# Add CORS middleware
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
@ -30,36 +30,36 @@ app.add_middleware(
allow_headers=["*"],
)
# 创建数据库表
# Create database tables
create_tables()
# 挂载静态文件
# Mount static files
app.mount("/static", StaticFiles(directory="app/static"), name="static")
# 设置模板
# Set up templates
templates = Jinja2Templates(directory="app/templates")
# 启动时间
# Startup time
start_time = datetime.now()
@app.get("/", response_class=HTMLResponse)
async def root(request: Request):
"""根路径 - 重定向到登录页"""
"""Root path - redirect to login page"""
return RedirectResponse(url="/login")
@app.get("/login", response_class=HTMLResponse)
async def login_page(request: Request):
"""登录页面"""
"""Login page"""
return templates.TemplateResponse("login.html", {"request": request})
@app.get("/dashboard", response_class=HTMLResponse)
async def dashboard_page(request: Request):
"""仪表板页面"""
"""Dashboard page"""
return templates.TemplateResponse("dashboard.html", {"request": request})
@app.post("/api/auth/login")
async def login(request: dict):
"""管理员登录"""
"""Admin login"""
admin_key = os.getenv("ADMIN_SECRET_KEY", "admin-secret-key-change-in-production")
if request.get("secret_key") != admin_key:
@ -68,7 +68,7 @@ async def login(request: dict):
detail="Invalid secret key"
)
# 生成 JWT 令牌
# Generate JWT token
token = auth_middleware.create_access_token(
data={"sub": "admin", "role": "admin"}
)
@ -77,21 +77,21 @@ async def login(request: dict):
@app.get("/api/stats")
async def get_stats(db: Session = Depends(get_db), current_user: dict = Depends(get_current_user)):
"""获取统计信息"""
"""Get statistics"""
try:
# 获取项目总数
# Get total number of projects
total_projects = db.query(ProjectMapping).count()
# 获取 API 密钥总数
# Get total number of API keys
total_api_keys = db.query(APIKey).count()
# 获取今日触发次数
# Get today's trigger count
today = datetime.now().date()
today_triggers = db.query(TriggerLog).filter(
TriggerLog.created_at >= today
).count()
# 获取成功触发次数
# Get successful trigger count
successful_triggers = db.query(TriggerLog).filter(
TriggerLog.status == "success"
).count()
@ -103,11 +103,11 @@ async def get_stats(db: Session = Depends(get_db), current_user: dict = Depends(
"successful_triggers": successful_triggers
}
except Exception as e:
raise HTTPException(status_code=500, detail=f"获取统计信息失败: {str(e)}")
raise HTTPException(status_code=500, detail=f"Failed to get statistics: {str(e)}")
@app.get("/api/keys", response_model=dict)
async def list_api_keys(db: Session = Depends(get_db), current_user: dict = Depends(get_current_user)):
"""获取所有 API 密钥(兼容前端)"""
"""Get all API keys (frontend compatible)"""
try:
keys = db.query(APIKey).order_by(APIKey.created_at.desc()).all()
return {
@ -122,7 +122,7 @@ async def list_api_keys(db: Session = Depends(get_db), current_user: dict = Depe
]
}
except Exception as e:
raise HTTPException(status_code=500, detail=f"获取 API 密钥失败: {str(e)}")
raise HTTPException(status_code=500, detail=f"Failed to get API keys: {str(e)}")
@app.post("/api/keys", response_model=dict)
async def create_api_key(
@ -130,12 +130,12 @@ async def create_api_key(
db: Session = Depends(get_db),
current_user: dict = Depends(get_current_user)
):
"""创建新的 API 密钥(兼容前端)"""
"""Create a new API key (frontend compatible)"""
try:
# 生成新的 API 密钥
# Generate new API key
api_key_value = auth_middleware.generate_api_key()
# 保存到数据库
# Save to database
db_key = APIKey(
key=api_key_value,
description=request.get("description", "")
@ -152,7 +152,7 @@ async def create_api_key(
"created_at": db_key.created_at.isoformat()
}
except Exception as e:
raise HTTPException(status_code=500, detail=f"创建 API 密钥失败: {str(e)}")
raise HTTPException(status_code=500, detail=f"Failed to create API key: {str(e)}")
@app.delete("/api/keys/{key_id}")
async def delete_api_key(
@ -160,25 +160,25 @@ async def delete_api_key(
db: Session = Depends(get_db),
current_user: dict = Depends(get_current_user)
):
"""删除 API 密钥(兼容前端)"""
"""Delete API key (frontend compatible)"""
try:
key = db.query(APIKey).filter(APIKey.id == key_id).first()
if not key:
raise HTTPException(status_code=404, detail="API 密钥不存在")
raise HTTPException(status_code=404, detail="API key does not exist")
db.delete(key)
db.commit()
return {"message": "API 密钥删除成功"}
return {"message": "API key deleted successfully"}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=f"删除 API 密钥失败: {str(e)}")
raise HTTPException(status_code=500, detail=f"Failed to delete API key: {str(e)}")
@app.get("/api/projects/", response_model=dict)
async def list_projects(db: Session = Depends(get_db), current_user: dict = Depends(get_current_user)):
"""获取所有项目(兼容前端)"""
"""Get all projects (frontend compatible)"""
try:
projects = db.query(ProjectMapping).order_by(ProjectMapping.created_at.desc()).all()
return {
@ -194,7 +194,7 @@ async def list_projects(db: Session = Depends(get_db), current_user: dict = Depe
]
}
except Exception as e:
raise HTTPException(status_code=500, detail=f"获取项目列表失败: {str(e)}")
raise HTTPException(status_code=500, detail=f"Failed to get project list: {str(e)}")
@app.post("/api/projects/", response_model=dict)
async def create_project(
@ -202,17 +202,17 @@ async def create_project(
db: Session = Depends(get_db),
current_user: dict = Depends(get_current_user)
):
"""创建新项目(兼容前端)"""
"""Create a new project (frontend compatible)"""
try:
# 检查项目是否已存在
# Check if project already exists
existing_project = db.query(ProjectMapping).filter(
ProjectMapping.repository_name == request["giteaRepo"]
).first()
if existing_project:
raise HTTPException(status_code=400, detail="项目已存在")
raise HTTPException(status_code=400, detail="Project already exists")
# 创建新项目
# Create new project
project = ProjectMapping(
repository_name=request["giteaRepo"],
default_job=request["jenkinsJob"]
@ -232,7 +232,7 @@ async def create_project(
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=f"创建项目失败: {str(e)}")
raise HTTPException(status_code=500, detail=f"Failed to create project: {str(e)}")
@app.delete("/api/projects/{project_id}")
async def delete_project(
@ -240,31 +240,31 @@ async def delete_project(
db: Session = Depends(get_db),
current_user: dict = Depends(get_current_user)
):
"""删除项目(兼容前端)"""
"""Delete project (frontend compatible)"""
try:
project = db.query(ProjectMapping).filter(ProjectMapping.id == project_id).first()
if not project:
raise HTTPException(status_code=404, detail="项目不存在")
raise HTTPException(status_code=404, detail="Project does not exist")
db.delete(project)
db.commit()
return {"message": "项目删除成功"}
return {"message": "Project deleted successfully"}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=f"删除项目失败: {str(e)}")
raise HTTPException(status_code=500, detail=f"Failed to delete project: {str(e)}")
@app.get("/health")
async def health_check():
"""健康检查端点"""
"""Health check endpoint"""
try:
# 计算运行时间
# Calculate uptime
uptime = datetime.now() - start_time
uptime_str = str(uptime).split('.')[0] # 移除微秒
uptime_str = str(uptime).split('.')[0] # Remove microseconds
# 获取内存使用情况
# Get memory usage
process = psutil.Process()
memory_info = process.memory_info()
memory_mb = memory_info.rss / 1024 / 1024
@ -292,21 +292,21 @@ async def get_logs(
db: Session = Depends(get_db),
current_user: dict = Depends(get_current_user)
):
"""获取日志(简化版本)"""
"""Get logs (simplified version)"""
try:
# 这里应该实现真正的日志查询逻辑
# 目前返回模拟数据
# Here should be the real log query logic
# Currently returns mock data
logs = [
{
"timestamp": datetime.now().isoformat(),
"level": "info",
"message": "系统运行正常"
"message": "System running normally"
}
]
return {"logs": logs}
except Exception as e:
raise HTTPException(status_code=500, detail=f"获取日志失败: {str(e)}")
raise HTTPException(status_code=500, detail=f"Failed to get logs: {str(e)}")
if __name__ == "__main__":
import uvicorn

View File

@ -1,17 +1,17 @@
// 全局变量存储 JWT 令牌
// Global variable to store JWT token
let authToken = localStorage.getItem('auth_token');
$(document).ready(function() {
// 检查认证状态
// Check authentication status
if (!authToken) {
window.location.href = '/login';
return;
}
// 设置 AJAX 默认配置
// Set AJAX default config
$.ajaxSetup({
beforeSend: function(xhr, settings) {
// 不为登录请求添加认证头
// Do not add auth header for login request
if (settings.url === '/api/auth/login') {
return;
}
@ -20,7 +20,7 @@ $(document).ready(function() {
}
},
error: function(xhr, status, error) {
// 如果收到 401重定向到登录页
// If 401 received, redirect to login page
if (xhr.status === 401) {
localStorage.removeItem('auth_token');
window.location.href = '/login';
@ -30,10 +30,10 @@ $(document).ready(function() {
}
});
// 初始化工具提示
// Initialize tooltips
$('[data-bs-toggle="tooltip"]').tooltip();
// 加载初始数据
// Load initial data
loadProjects();
loadAPIKeys();
loadLogs();
@ -41,10 +41,10 @@ $(document).ready(function() {
loadHealthDetails();
loadStatsDetails();
// 设置定期健康检查
// Set periodic health check
setInterval(checkHealth, 30000);
// 项目管理
// Project management
$('#addProjectForm').on('submit', function(e) {
e.preventDefault();
const projectData = {
@ -62,13 +62,13 @@ $(document).ready(function() {
$('#addProjectModal').modal('hide');
$('#addProjectForm')[0].reset();
loadProjects();
showSuccess('项目添加成功');
showSuccess('Project added successfully');
},
error: handleAjaxError
});
});
// API 密钥管理
// API key management
$('#generateKeyForm').on('submit', function(e) {
e.preventDefault();
$.ajax({
@ -80,16 +80,16 @@ $(document).ready(function() {
$('#generateKeyModal').modal('hide');
$('#generateKeyForm')[0].reset();
loadAPIKeys();
showSuccess('API 密钥生成成功');
showSuccess('API key generated successfully');
// 显示新生成的密钥
// Show newly generated key
showApiKeyModal(response.key);
},
error: handleAjaxError
});
});
// 日志查询
// Log query
$('#logQueryForm').on('submit', function(e) {
e.preventDefault();
loadLogs({
@ -100,7 +100,7 @@ $(document).ready(function() {
});
});
// 标签页切换
// Tab switching
$('.nav-link').on('click', function() {
$('.nav-link').removeClass('active');
$(this).addClass('active');
@ -121,7 +121,7 @@ function loadProjects() {
<td>${escapeHtml(project.giteaRepo)}</td>
<td>
<button class="btn btn-sm btn-danger" onclick="deleteProject(${project.id})">
<i class="bi bi-trash"></i>
<i class="bi bi-trash"></i> Delete
</button>
</td>
</tr>
@ -140,12 +140,12 @@ function loadAPIKeys() {
data.keys.forEach(function(key) {
tbody.append(`
<tr>
<td>${escapeHtml(key.description || '无描述')}</td>
<td>${escapeHtml(key.description || 'No description')}</td>
<td><code class="api-key">${escapeHtml(key.key)}</code></td>
<td>${new Date(key.created_at).toLocaleString('zh-CN')}</td>
<td>
<button class="btn btn-sm btn-danger" onclick="revokeKey(${key.id})">
<i class="bi bi-trash"></i>
<i class="bi bi-trash"></i> Revoke
</button>
</td>
</tr>
@ -178,7 +178,7 @@ function loadLogs(query = {}) {
`);
});
} else {
logContainer.append('<div class="text-muted">暂无日志记录</div>');
logContainer.append('<div class="text-muted">No log records</div>');
}
})
.fail(handleAjaxError);
@ -190,12 +190,12 @@ function checkHealth() {
const indicator = $('.health-indicator');
indicator.removeClass('healthy unhealthy')
.addClass(data.status === 'healthy' ? 'healthy' : 'unhealthy');
$('#healthStatus').text(data.status === 'healthy' ? '健康' : '异常');
$('#healthStatus').text(data.status === 'healthy' ? 'Healthy' : 'Unhealthy');
})
.fail(function() {
const indicator = $('.health-indicator');
indicator.removeClass('healthy').addClass('unhealthy');
$('#healthStatus').text('异常');
$('#healthStatus').text('Unhealthy');
});
}
@ -205,24 +205,24 @@ function loadHealthDetails() {
const healthDetails = $('#healthDetails');
healthDetails.html(`
<div class="mb-3">
<strong>状态:</strong>
<strong>Status:</strong>
<span class="badge ${data.status === 'healthy' ? 'bg-success' : 'bg-danger'}">
${data.status === 'healthy' ? '健康' : '异常'}
${data.status === 'healthy' ? 'Healthy' : 'Unhealthy'}
</span>
</div>
<div class="mb-3">
<strong>版本:</strong> ${data.version || ''}
<strong>Version:</strong> ${data.version || 'Unknown'}
</div>
<div class="mb-3">
<strong>启动时间:</strong> ${data.uptime || ''}
<strong>Uptime:</strong> ${data.uptime || 'Unknown'}
</div>
<div class="mb-3">
<strong>内存使用:</strong> ${data.memory || ''}
<strong>Memory Usage:</strong> ${data.memory || 'Unknown'}
</div>
`);
})
.fail(function() {
$('#healthDetails').html('<div class="text-danger">无法获取健康状态</div>');
$('#healthDetails').html('<div class="text-danger">Unable to get health status</div>');
});
}
@ -232,75 +232,75 @@ function loadStatsDetails() {
const statsDetails = $('#statsDetails');
statsDetails.html(`
<div class="mb-3">
<strong>总项目数:</strong> ${data.total_projects || 0}
<strong>Total Projects:</strong> ${data.total_projects || 0}
</div>
<div class="mb-3">
<strong>API 密钥数:</strong> ${data.total_api_keys || 0}
<strong>API Keys:</strong> ${data.total_api_keys || 0}
</div>
<div class="mb-3">
<strong>今日触发次数:</strong> ${data.today_triggers || 0}
<strong>Today's Triggers:</strong> ${data.today_triggers || 0}
</div>
<div class="mb-3">
<strong>成功触发次数:</strong> ${data.successful_triggers || 0}
<strong>Successful Triggers:</strong> ${data.successful_triggers || 0}
</div>
`);
})
.fail(function() {
$('#statsDetails').html('<div class="text-danger">无法获取统计信息</div>');
$('#statsDetails').html('<div class="text-danger">Unable to get statistics</div>');
});
}
function deleteProject(id) {
if (!confirm('确定要删除这个项目吗?')) return;
if (!confirm('Are you sure you want to delete this project?')) return;
$.ajax({
url: `/api/projects/${id}`,
method: 'DELETE',
success: function() {
loadProjects();
showSuccess('项目删除成功');
showSuccess('Project deleted successfully');
},
error: handleAjaxError
});
}
function revokeKey(id) {
if (!confirm('确定要撤销这个 API 密钥吗?')) return;
if (!confirm('Are you sure you want to revoke this API key?')) return;
$.ajax({
url: `/api/keys/${id}`,
method: 'DELETE',
success: function() {
loadAPIKeys();
showSuccess('API 密钥撤销成功');
showSuccess('API key revoked successfully');
},
error: handleAjaxError
});
}
function showApiKeyModal(key) {
// 创建模态框显示新生成的密钥
// Create modal to show newly generated key
const modal = $(`
<div class="modal fade" id="newApiKeyModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">API 密钥</h5>
<h5 class="modal-title">New API Key</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="alert alert-warning">
<strong>重要提示:</strong>
<strong>Important:</strong> Please save this key, as it will only be shown once!
</div>
<div class="mb-3">
<label class="form-label">API 密钥:</label>
<label class="form-label">API Key:</label>
<input type="text" class="form-control" value="${key}" readonly>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" onclick="copyToClipboard('${key}')">
复制到剪贴板
Copy to Clipboard
</button>
</div>
</div>
@ -318,19 +318,19 @@ function showApiKeyModal(key) {
function copyToClipboard(text) {
navigator.clipboard.writeText(text).then(function() {
showSuccess('已复制到剪贴板');
showSuccess('Copied to clipboard');
}, function() {
showError('复制失败');
showError('Copy failed');
});
}
function handleAjaxError(jqXHR, textStatus, errorThrown) {
const message = jqXHR.responseJSON?.detail || errorThrown || '发生错误';
showError(`错误: ${message}`);
const message = jqXHR.responseJSON?.detail || errorThrown || 'An error occurred';
showError(`Error: ${message}`);
}
function showSuccess(message) {
// 创建成功提示
// Create success alert
const alert = $(`
<div class="alert alert-success alert-dismissible fade show" role="alert">
${message}
@ -340,14 +340,14 @@ function showSuccess(message) {
$('.main-content').prepend(alert);
// 3秒后自动消失
// Auto dismiss after 3 seconds
setTimeout(function() {
alert.alert('close');
}, 3000);
}
function showError(message) {
// 创建错误提示
// Create error alert
const alert = $(`
<div class="alert alert-danger alert-dismissible fade show" role="alert">
${message}
@ -357,7 +357,7 @@ function showError(message) {
$('.main-content').prepend(alert);
// 5秒后自动消失
// Auto dismiss after 5 seconds
setTimeout(function() {
alert.alert('close');
}, 5000);

View File

@ -1,9 +1,9 @@
<!DOCTYPE html>
<html lang="zh-CN">
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>仪表板 - Gitea Webhook Ambassador</title>
<title>Dashboard - Gitea Webhook Ambassador</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.7.2/font/bootstrap-icons.css" rel="stylesheet">
<style>
@ -111,7 +111,7 @@
<div class="nav-item text-nowrap">
<span class="px-3 text-white">
<span class="health-indicator"></span>
<span id="healthStatus">检查中...</span>
<span id="healthStatus">Checking...</span>
</span>
</div>
</div>
@ -124,22 +124,22 @@
<ul class="nav flex-column">
<li class="nav-item">
<a class="nav-link active" href="#projects" data-bs-toggle="tab">
<i class="bi bi-folder"></i> 项目管理
<i class="bi bi-folder"></i> Project Management
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#api-keys" data-bs-toggle="tab">
<i class="bi bi-key"></i> API 密钥
<i class="bi bi-key"></i> API Keys
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#logs" data-bs-toggle="tab">
<i class="bi bi-journal-text"></i> 日志查看
<i class="bi bi-journal-text"></i> Logs
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#health" data-bs-toggle="tab">
<i class="bi bi-heart-pulse"></i> 健康状态
<i class="bi bi-heart-pulse"></i> Health Status
</a>
</li>
</ul>
@ -148,22 +148,22 @@
<main class="col-md-9 ms-sm-auto col-lg-10 px-md-4 main-content">
<div class="tab-content" id="myTabContent">
<!-- 项目管理 Tab -->
<!-- Project Management Tab -->
<div class="tab-pane fade show active" id="projects">
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">项目管理</h1>
<h1 class="h2">Project Management</h1>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addProjectModal">
<i class="bi bi-plus"></i> 添加项目
<i class="bi bi-plus"></i> Add Project
</button>
</div>
<div class="table-responsive">
<table class="table table-striped" id="projectsTable">
<thead>
<tr>
<th>项目名称</th>
<th>Jenkins 任务</th>
<th>Gitea 仓库</th>
<th>操作</th>
<th>Project Name</th>
<th>Jenkins Job</th>
<th>Gitea Repo</th>
<th>Action</th>
</tr>
</thead>
<tbody></tbody>
@ -171,22 +171,22 @@
</div>
</div>
<!-- API 密钥 Tab -->
<!-- API Keys Tab -->
<div class="tab-pane fade" id="api-keys">
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">API 密钥管理</h1>
<h1 class="h2">API Key Management</h1>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#generateKeyModal">
<i class="bi bi-plus"></i> 生成新密钥
<i class="bi bi-plus"></i> Generate New Key
</button>
</div>
<div class="table-responsive">
<table class="table table-striped" id="apiKeysTable">
<thead>
<tr>
<th>描述</th>
<th>密钥</th>
<th>创建时间</th>
<th>操作</th>
<th>Description</th>
<th>Key</th>
<th>Created At</th>
<th>Action</th>
</tr>
</thead>
<tbody></tbody>
@ -194,52 +194,52 @@
</div>
</div>
<!-- 日志查看 Tab -->
<!-- Logs Tab -->
<div class="tab-pane fade" id="logs">
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">日志查看</h1>
<h1 class="h2">Logs</h1>
</div>
<form id="logQueryForm" class="row g-3 mb-3">
<div class="col-md-3">
<label for="startTime" class="form-label">开始时间</label>
<label for="startTime" class="form-label">Start Time</label>
<input type="datetime-local" class="form-control" id="startTime">
</div>
<div class="col-md-3">
<label for="endTime" class="form-label">结束时间</label>
<label for="endTime" class="form-label">End Time</label>
<input type="datetime-local" class="form-control" id="endTime">
</div>
<div class="col-md-2">
<label for="logLevel" class="form-label">日志级别</label>
<label for="logLevel" class="form-label">Log Level</label>
<select class="form-select" id="logLevel">
<option value="">全部</option>
<option value="error">错误</option>
<option value="warn">警告</option>
<option value="info">信息</option>
<option value="debug">调试</option>
<option value="">All</option>
<option value="error">Error</option>
<option value="warn">Warning</option>
<option value="info">Info</option>
<option value="debug">Debug</option>
</select>
</div>
<div class="col-md-3">
<label for="logQuery" class="form-label">搜索关键词</label>
<input type="text" class="form-control" id="logQuery" placeholder="搜索日志...">
<label for="logQuery" class="form-label">Search Keyword</label>
<input type="text" class="form-control" id="logQuery" placeholder="Search logs...">
</div>
<div class="col-md-1">
<label class="form-label">&nbsp;</label>
<button type="submit" class="btn btn-primary w-100">搜索</button>
<button type="submit" class="btn btn-primary w-100">Search</button>
</div>
</form>
<div id="logEntries" class="border rounded p-3 bg-light" style="max-height: 500px; overflow-y: auto;"></div>
</div>
<!-- 健康状态 Tab -->
<!-- Health Status Tab -->
<div class="tab-pane fade" id="health">
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">健康状态</h1>
<h1 class="h2">Health Status</h1>
</div>
<div class="row">
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">服务状态</h5>
<h5 class="card-title mb-0">Service Status</h5>
</div>
<div class="card-body">
<div id="healthDetails"></div>
@ -249,7 +249,7 @@
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">统计信息</h5>
<h5 class="card-title mb-0">Statistics</h5>
</div>
<div class="card-body">
<div id="statsDetails"></div>
@ -263,56 +263,56 @@
</div>
</div>
<!-- 添加项目模态框 -->
<!-- Add Project Modal -->
<div class="modal fade" id="addProjectModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">添加新项目</h5>
<h5 class="modal-title">Add New Project</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<form id="addProjectForm">
<div class="modal-body">
<div class="mb-3">
<label for="projectName" class="form-label">项目名称</label>
<label for="projectName" class="form-label">Project Name</label>
<input type="text" class="form-control" id="projectName" required>
</div>
<div class="mb-3">
<label for="jenkinsJob" class="form-label">Jenkins 任务</label>
<label for="jenkinsJob" class="form-label">Jenkins Job</label>
<input type="text" class="form-control" id="jenkinsJob" required>
</div>
<div class="mb-3">
<label for="giteaRepo" class="form-label">Gitea 仓库</label>
<label for="giteaRepo" class="form-label">Gitea Repo</label>
<input type="text" class="form-control" id="giteaRepo" placeholder="owner/repo" required>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button type="submit" class="btn btn-primary">添加项目</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">Add Project</button>
</div>
</form>
</div>
</div>
</div>
<!-- 生成 API 密钥模态框 -->
<!-- Generate API Key Modal -->
<div class="modal fade" id="generateKeyModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">生成新 API 密钥</h5>
<h5 class="modal-title">Generate New API Key</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<form id="generateKeyForm">
<div class="modal-body">
<div class="mb-3">
<label for="keyDescription" class="form-label">密钥描述</label>
<label for="keyDescription" class="form-label">Key Description</label>
<input type="text" class="form-control" id="keyDescription" required>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button type="submit" class="btn btn-primary">生成密钥</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">Generate Key</button>
</div>
</form>
</div>

View File

@ -1,9 +1,9 @@
<!DOCTYPE html>
<html lang="zh-CN">
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>登录 - Gitea Webhook Ambassador</title>
<title>Login - Gitea Webhook Ambassador</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
.login-container {
@ -61,7 +61,7 @@
<div class="login-form">
<div class="login-header">
<h1>🔗 Gitea Webhook Ambassador</h1>
<p>高性能的 Gitea 到 Jenkins 的 Webhook 服务</p>
<p>High-performance Gitea to Jenkins Webhook Service</p>
</div>
<form id="loginForm">
@ -69,20 +69,19 @@
</div>
<div class="form-floating">
<input type="password" class="form-control" id="secret_key" name="secret_key"
placeholder="管理员密钥" required>
<label for="secret_key">管理员密钥</label>
<input type="password" class="form-control" id="secret_key" name="secret_key" placeholder="Admin Secret Key" required>
<label for="secret_key">Admin Secret Key</label>
</div>
<button class="btn btn-primary btn-login" type="submit">
<span id="loginBtnText">登录</span>
<span id="loginBtnText">Login</span>
<span id="loginBtnSpinner" class="spinner-border spinner-border-sm" style="display: none;"></span>
</button>
</form>
<div class="text-center mt-3">
<small class="text-muted">
使用管理员密钥进行身份验证
Use the admin secret key for authentication
</small>
</div>
</div>
@ -92,39 +91,39 @@
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<script>
$(document).ready(function() {
// 检查是否已登录
// Check if already logged in
const token = localStorage.getItem('auth_token');
if (token) {
window.location.href = '/dashboard';
return;
}
// 检查 URL 参数中的 secret_key
// Check secret_key in URL params
const urlParams = new URLSearchParams(window.location.search);
const secretKeyFromUrl = urlParams.get('secret_key');
if (secretKeyFromUrl) {
$('#secret_key').val(secretKeyFromUrl);
// 自动提交登录
// Auto-submit login
$('#loginForm').submit();
return;
}
// 处理登录表单提交
// Handle login form submit
$('#loginForm').on('submit', function(e) {
e.preventDefault();
const secretKey = $('#secret_key').val();
if (!secretKey) {
showError('请输入管理员密钥');
showError('Please enter the admin secret key');
return;
}
// 显示加载状态
// Show loading state
$('#loginBtnText').hide();
$('#loginBtnSpinner').show();
$('#loginError').hide();
// 发送登录请求
// Send login request
$.ajax({
url: '/api/auth/login',
method: 'POST',
@ -132,16 +131,16 @@
data: JSON.stringify({ secret_key: secretKey }),
success: function(response) {
if (response && response.token) {
// 保存令牌并跳转
// Save token and redirect
localStorage.setItem('auth_token', response.token);
window.location.href = '/dashboard';
} else {
showError('服务器响应无效');
showError('Invalid server response');
}
},
error: function(xhr) {
console.error('登录错误:', xhr);
let errorMsg = '登录失败,请重试';
console.error('Login error:', xhr);
let errorMsg = 'Login failed, please try again';
if (xhr.responseJSON && xhr.responseJSON.detail) {
errorMsg = xhr.responseJSON.detail;
@ -151,7 +150,7 @@
$('#secret_key').val('').focus();
},
complete: function() {
// 恢复按钮状态
// Restore button state
$('#loginBtnText').show();
$('#loginBtnSpinner').hide();
}
@ -162,7 +161,7 @@
$('#loginError').text(message).show();
}
// 回车键提交
// Enter key submit
$('#secret_key').on('keypress', function(e) {
if (e.which === 13) {
$('#loginForm').submit();

View File

@ -1,140 +0,0 @@
library 'first-class-pipeline'
executeFreeleapsPipeline {
serviceName = 'freeleaps'
environmentSlug = 'alpha'
serviceGitBranch = 'dev'
serviceGitRepo = "https://gitea.freeleaps.mathmast.com/freeleaps/freeleaps-service-hub.git"
serviceGitRepoType = 'monorepo'
serviceGitCredentialsId = 'freeleaps-repos-gitea-credentails'
executeMode = 'fully'
commitMessageLintEnabled = false
components = [
[
name: 'authentication',
root: 'apps/authentication',
language: 'python',
dependenciesManager: 'pip',
requirementsFile: 'requirements.txt',
buildCacheEnabled: true,
buildAgentImage: 'python:3.10-slim-buster',
buildArtifacts: ['.'],
lintEnabled: false,
sastEnabled: false,
imageRegistry: 'docker.io',
imageRepository: 'freeleaps',
imageName: 'authentication',
imageBuilder: 'dind',
dockerfilePath: 'Dockerfile',
imageBuildRoot: '.',
imageReleaseArchitectures: ['linux/amd64', 'linux/arm64/v8'],
registryCredentialsId: 'freeleaps-devops-docker-hub-credentials',
semanticReleaseEnabled: true
],
[
name: 'centralStorage',
root: 'apps/central_storage',
language: 'python',
dependenciesManager: 'pip',
requirementsFile: 'requirements.txt',
buildAgentImage: 'python:3.10-slim-buster',
buildArtifacts: ['.'],
buildCacheEnabled: true,
lintEnabled: false,
sastEnabled: false,
imageRegistry: 'docker.io',
imageRepository: 'freeleaps',
imageName: 'central_storage',
imageBuilder: 'dind',
dockerfilePath: 'Dockerfile',
imageBuildRoot: '.',
imageReleaseArchitectures: ['linux/amd64', 'linux/arm64/v8'],
registryCredentialsId: 'freeleaps-devops-docker-hub-credentials',
semanticReleaseEnabled: true
],
[
name: 'content',
root: 'apps/content',
language: 'python',
dependenciesManager: 'pip',
requirementsFile: 'requirements.txt',
buildAgentImage: 'python:3.10-slim-buster',
buildArtifacts: ['.'],
buildCacheEnabled: true,
lintEnabled: false,
sastEnabled: false,
imageRegistry: 'docker.io',
imageRepository: 'freeleaps',
imageName: 'content',
imageBuilder: 'dind',
dockerfilePath: 'Dockerfile',
imageBuildRoot: '.',
imageReleaseArchitectures: ['linux/amd64', 'linux/arm64/v8'],
registryCredentialsId: 'freeleaps-devops-docker-hub-credentials',
semanticReleaseEnabled: true
],
[
name: 'notification',
root: 'apps/notification',
language: 'python',
dependenciesManager: 'pip',
requirementsFile: 'requirements.txt',
buildAgentImage: 'python:3.10-slim-buster',
buildArtifacts: ['.'],
buildCacheEnabled: true,
lintEnabled: false,
sastEnabled: false,
imageRegistry: 'docker.io',
imageRepository: 'freeleaps',
imageName: 'notification',
imageBuilder: 'dind',
dockerfilePath: 'Dockerfile',
imageBuildRoot: '.',
imageReleaseArchitectures: ['linux/amd64', 'linux/arm64/v8'],
registryCredentialsId: 'freeleaps-devops-docker-hub-credentials',
semanticReleaseEnabled: true
],
[
name: 'payment',
root: 'apps/payment',
language: 'python',
dependenciesManager: 'pip',
requirementsFile: 'requirements.txt',
buildAgentImage: 'python:3.10-slim-buster',
buildArtifacts: ['.'],
buildCacheEnabled: true,
lintEnabled: false,
sastEnabled: false,
imageRegistry: 'docker.io',
imageRepository: 'freeleaps',
imageName: 'payment',
imageBuilder: 'dind',
dockerfilePath: 'Dockerfile',
imageBuildRoot: '.',
imageReleaseArchitectures: ['linux/amd64', 'linux/arm64/v8'],
registryCredentialsId: 'freeleaps-devops-docker-hub-credentials',
semanticReleaseEnabled: true
],
[
name: 'devops',
root: 'apps/devops',
language: 'python',
dependenciesManager: 'pip',
requirementsFile: 'requirements.txt',
buildCacheEnabled: true,
buildAgentImage: 'python:3.12-slim',
buildArtifacts: ['.'],
lintEnabled: true,
sastEnabled: true,
imageRegistry: 'docker.io',
imageRepository: 'freeleaps',
imageName: 'devops',
imageBuilder: 'dind',
dockerfilePath: 'Dockerfile',
imageBuildRoot: '.',
imageReleaseArchitectures: ['linux/amd64', 'linux/arm64/v8'],
registryCredentialsId: 'freeleaps-devops-docker-hub-credentials',
semanticReleaseEnabled: true
]
]
}

View File

@ -1 +0,0 @@

View File

@ -114,6 +114,27 @@ executeFreeleapsPipeline {
imageReleaseArchitectures: ['linux/amd64', 'linux/arm64/v8'],
registryCredentialsId: 'freeleaps-devops-docker-hub-credentials',
semanticReleaseEnabled: true
],
[
name: 'devops',
root: 'apps/devops',
language: 'python',
dependenciesManager: 'pip',
requirementsFile: 'requirements.txt',
buildCacheEnabled: true,
buildAgentImage: 'python:3.12-slim',
buildArtifacts: ['.'],
lintEnabled: false,
sastEnabled: false,
imageRegistry: 'docker.io',
imageRepository: 'freeleaps',
imageName: 'devops',
imageBuilder: 'dind',
dockerfilePath: 'Dockerfile',
imageBuildRoot: '.',
imageReleaseArchitectures: ['linux/amd64', 'linux/arm64/v8'],
registryCredentialsId: 'freeleaps-devops-docker-hub-credentials',
semanticReleaseEnabled: true
]
]
}

View File

@ -0,0 +1,6 @@
apiVersion: v2
name: devops
description: A Helm Chart of devops, which part of Freeleaps Platform, powered by Freeleaps.
type: application
version: 0.0.1
appVersion: "0.0.1"

View File

@ -0,0 +1,27 @@
{{ $namespace := .Release.Namespace }}
{{ $appVersion := .Chart.AppVersion | quote }}
{{ $releaseCertificate := .Release.Service }}
{{ $releaseName := .Release.Name }}
{{- range $ingress := .Values.authentication.ingresses }}
{{- if not $ingress.tls.exists }}
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: {{ $ingress.name }}
namespace: {{ $namespace }}
labels:
app.kubernetes.io/version: {{ $appVersion }}
app.kubernetes.io/name: {{ $ingress.name | quote }}
app.kubernetes.io/managed-by: {{ $releaseCertificate }}
app.kubernetes.io/instance: {{ $releaseName }}
spec:
commonName: {{ $ingress.host }}
dnsNames:
- {{ $ingress.host }}
issuerRef:
name: {{ $ingress.tls.issuerRef.name }}
kind: {{ $ingress.tls.issuerRef.kind }}
secretName: {{ $ingress.tls.name }}
{{- end }}
{{- end }}

View File

@ -0,0 +1,118 @@
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
app.kubernetes.io/name: "devops"
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/instance: {{ .Release.Name }}
# {{- if .Values.logIngest.enabled }}
# annotations:
# opentelemetry.io/config-checksum: {{ include (print $.Template.BasePath "/authentication/opentelemetry.yaml") . | sha256sum }}
# {{- end }}
name: "devops"
namespace: {{ .Release.Namespace | quote }}
spec:
selector:
matchLabels:
app.kubernetes.io/name: "devops"
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
replicas: {{ .Values.devops.replicas }}
template:
metadata:
labels:
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
app.kubernetes.io/name: "devops"
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/instance: {{ .Release.Name }}
annotations:
app.kubernetes.io/config-checksum: {{ include (print $.Template.BasePath "/devops/devops-config.yaml") . | sha256sum }}
{{- if .Values.logIngest.enabled }}
opentelemetry.io/config-checksum: {{ include (print $.Template.BasePath "/devops/opentelemetry.yaml") . | sha256sum }}
sidecar.opentelemetry.io/inject: "{{ .Release.Namespace}}/{{ .Release.Name }}-opentelemetry-collector"
{{- end }}
spec:
# {{- if .Values.logIngest.enabled }}
# serviceAccountName: "{{ .Release.Name }}-otel-collector"
# {{- end }}
containers:
- name: "devops"
image: "{{ coalesce .Values.devops.image.registry .Values.global.registry "docker.io"}}/{{ coalesce .Values.devops.image.repository .Values.global.repository }}/{{ .Values.devops.image.name }}:{{ .Values.devops.image.tag | default "latest" }}"
imagePullPolicy: {{ .Values.devops.image.imagePullPolicy | default "IfNotPresent" }}
ports:
{{- range $port := .Values.devops.ports }}
- containerPort: {{ $port.containerPort }}
name: {{ $port.name }}
protocol: {{ $port.protocol }}
{{- end }}
{{- if .Values.devops.resources }}
resources:
{{- toYaml .Values.devops.resources | nindent 12 }}
{{- end }}
{{- if .Values.devops.probes }}
{{- if and (.Values.devops.probes.liveness) (eq .Values.devops.probes.liveness.type "httpGet") }}
livenessProbe:
httpGet:
path: {{ .Values.devops.probes.liveness.config.path }}
port: {{ .Values.devops.probes.liveness.config.port }}
{{- if .Values.devops.probes.liveness.config.initialDelaySeconds }}
initialDelaySeconds: {{ .Values.devops.probes.liveness.config.initialDelaySeconds }}
{{- end }}
{{- if .Values.devops.probes.liveness.config.periodSeconds }}
periodSeconds: {{ .Values.devops.probes.liveness.config.periodSeconds }}
{{- end }}
{{- if .Values.devops.probes.liveness.config.timeoutSeconds }}
timeoutSeconds: {{ .Values.devops.probes.liveness.config.timeoutSeconds }}
{{- end }}
{{- if .Values.devops.probes.liveness.config.successThreshold }}
successThreshold: {{ .Values.devops.probes.liveness.config.successThreshold }}
{{- end }}
{{- if .Values.devops.probes.liveness.config.failureThreshold }}
failureThreshold: {{ .Values.devops.probes.liveness.config.failureThreshold }}
{{- end }}
{{- if .Values.devops.probes.liveness.config.terminationGracePeriodSeconds }}
terminationGracePeriodSeconds: {{ .Values.devops.probes.liveness.config.terminationGracePeriodSeconds }}
{{- end }}
{{- end }}
{{- if and (.Values.devops.probes.readiness) (eq .Values.devops.probes.readiness.type "httpGet") }}
readinessProbe:
httpGet:
path: {{ .Values.devops.probes.readiness.config.path }}
port: {{ .Values.devops.probes.readiness.config.port }}
{{- if .Values.devops.probes.readiness.config.initialDelaySeconds }}
initialDelaySeconds: {{ .Values.devops.probes.readiness.config.initialDelaySeconds }}
{{- end }}
{{- if .Values.devops.probes.readiness.config.periodSeconds }}
periodSeconds: {{ .Values.devops.probes.readiness.config.periodSeconds }}
{{- end }}
{{- if .Values.devops.probes.readiness.config.timeoutSeconds }}
timeoutSeconds: {{ .Values.devops.probes.readiness.config.timeoutSeconds }}
{{- end }}
{{- if .Values.devops.probes.readiness.config.successThreshold }}
successThreshold: {{ .Values.devops.probes.readiness.config.successThreshold }}
{{- end }}
{{- if .Values.devops.probes.readiness.config.failureThreshold }}
failureThreshold: {{ .Values.devops.probes.readiness.config.failureThreshold }}
{{- end }}
{{- end }}
{{- end}}
env:
{{- range $key, $value := .Values.devops.configs }}
- name: {{ $key | snakecase | upper }}
valueFrom:
secretKeyRef:
name: devops-config
key: {{ $key | snakecase | upper }}
{{- end }}
# {{- if .Values.logIngest.enabled }}
# volumeMounts:
# - name: app-logs
# mountPath: {{ .Values.logIngest.logPath }}
# {{- end }}
# {{- if .Values.logIngest.enabled }}
# volumes:
# - name: app-logs
# emptyDir: {}
# {{- end }}

View File

@ -0,0 +1,28 @@
apiVersion: v1
kind: Secret
metadata:
name: devops-config
namespace: {{ .Release.Namespace }}
type: Opaque
data:
TZ: {{ .Values.devops.configs.tz | b64enc | quote }}
APP_NAME: {{ .Values.devops.configs.appName | b64enc | quote }}
JWT_SECRET_KEY: {{ .Values.devops.configs.jwtSecretKey | b64enc | quote }}
JWT_ALGORITHM: {{ .Values.devops.configs.jwtAlgorithm | b64enc | quote }}
ACCESS_TOKEN_EXPIRE_MINUTES: {{ .Values.devops.configs.accessTokenExpireMinutes | toString | b64enc | quote }}
REFRESH_TOKEN_EXPIRE_DAYS: {{ .Values.devops.configs.refreshTokenExpireDays | toString | b64enc | quote }}
MONGODB_NAME: {{ .Values.devops.configs.mongodbName | b64enc | quote }}
MONGODB_PORT: {{ .Values.devops.configs.mongodbPort | toString | b64enc | quote }}
MONGODB_URI: {{ .Values.devops.configs.mongodbUri | b64enc | quote }}
METRICS_ENABLED: {{ .Values.devops.configs.metricsEnabled | toString | b64enc | quote }}
PROBES_ENABLED: {{ .Values.devops.configs.probesEnabled | toString | b64enc | quote }}
BASE_GITEA_URL: {{ .Values.devops.configs.baseGiteaUrl | b64enc | quote }}
BASE_RECONCILE_URL: {{ .Values.devops.configs.baseReconcileUrl | b64enc | quote }}
BASE_LOKI_URL: {{ .Values.devops.configs.baseLokiUrl | b64enc | quote }}
LOG_BASE_PATH: {{ .Values.devops.configs.logBasePath | b64enc | quote }}
LOG_RETENTION: {{ .Values.devops.configs.logRetention | b64enc | quote }}
LOG_ROTATION: {{ .Values.devops.configs.logRotation | b64enc | quote }}
LOG_BACKUP_FILES: {{ .Values.devops.configs.logBackupFiles | toString | b64enc | quote }}
LOG_ROTATION_BYTES: {{ .Values.devops.configs.logRotationBytes | toString | b64enc | quote }}
MOCK_MODE: {{ .Values.devops.configs.mockMode | toString | b64enc | quote }}
MOCK_RESPONSE_DELAY: {{ .Values.devops.configs.mockResponseDelay | toString | b64enc | quote }}

View File

@ -0,0 +1,36 @@
{{ $namespace := .Release.Namespace }}
{{ $appVersion := .Chart.AppVersion | quote }}
{{ $releaseIngress := .Release.Service }}
{{ $releaseName := .Release.Name }}
{{- range $ingress := .Values.authentication.ingresses }}
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ $ingress.name }}
namespace: {{ $namespace }}
labels:
app.kubernetes.io/version: {{ $appVersion }}
app.kubernetes.io/name: {{ $ingress.name | quote }}
app.kubernetes.io/managed-by: {{ $releaseIngress }}
app.kubernetes.io/instance: {{ $releaseName }}
spec:
{{- if $ingress.class }}
ingressClassName: {{ $ingress.class }}
{{- end }}
{{- if $ingress.tls }}
tls:
- hosts:
- {{ $ingress.host }}
{{- if $ingress.tls.exists }}
secretName: {{ $ingress.tls.secretRef.name }}
{{- else }}
secretName: {{ $ingress.tls.name }}
{{- end }}
{{- end }}
rules:
- host: {{ $ingress.host }}
http:
paths:
{{- toYaml $ingress.rules | nindent 10 }}
{{- end }}

View File

@ -0,0 +1,26 @@
{{ $namespace := .Release.Namespace }}
{{ $appVersion := .Chart.AppVersion | quote }}
{{ $releaseService := .Release.Service }}
{{ $releaseName := .Release.Name }}
{{- range $service := .Values.authentication.services }}
---
apiVersion: v1
kind: Service
metadata:
name: {{ $service.name }}
namespace: {{ $namespace }}
labels:
app.kubernetes.io/version: {{ $appVersion }}
app.kubernetes.io/name: {{ $service.name | quote }}
app.kubernetes.io/managed-by: {{ $releaseService }}
app.kubernetes.io/instance: {{ $releaseName }}
spec:
ports:
- port: {{ $service.port }}
targetPort: {{ $service.targetPort }}
selector:
app.kubernetes.io/version: {{ $appVersion }}
app.kubernetes.io/name: "authentication"
app.kubernetes.io/managed-by: {{ $releaseService }}
app.kubernetes.io/instance: {{ $releaseName }}
{{- end }}

View File

@ -0,0 +1,40 @@
{{ $namespace := .Release.Namespace }}
{{ $appVersion := .Chart.AppVersion | quote }}
{{ $releaseService := .Release.Service }}
{{ $releaseName := .Release.Name }}
{{- range $service := .Values.authentication.services }}
{{- if $service.serviceMonitor.enabled }}
---
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: {{ $service.name }}-monitor
namespace: {{ $service.serviceMonitor.namespace }}
labels:
app.kubernetes.io/version: {{ $appVersion }}
app.kubernetes.io/name: {{ $service.name }}-monitor
app.kubernetes.io/managed-by: {{ $releaseService }}
app.kubernetes.io/instance: {{ $releaseName }}
{{- if $service.serviceMonitor.labels }}
{{- toYaml $service.serviceMonitor.labels | nindent 4 }}
{{- end }}
spec:
endpoints:
- path: /api/_/metrics
targetPort: {{ $service.targetPort }}
{{- if $service.serviceMonitor.interval }}
interval: {{ $service.serviceMonitor.interval }}
{{- end }}
{{- if $service.serviceMonitor.scrapeTimeout }}
scrapeTimeout: {{ $service.serviceMonitor.scrapeTimeout }}
{{- end }}
namespaceSelector:
matchNames:
- {{ $namespace | quote }}
selector:
matchLabels:
app.kubernetes.io/name: {{ $service.name }}
app.kubernetes.io/instance: {{ $releaseName }}
{{- end }}
{{- end }}

View File

@ -0,0 +1,32 @@
{{- if .Values.devops.vpa }}
---
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: {{ .Release.Name }}-vpa
namespace: {{ .Release.Namespace }}
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: devops
resourcePolicy:
containerPolicies:
- containerName: '*'
{{- if .Values.devops.vpa.minAllowed.enabled }}
minAllowed:
cpu: {{ .Values.devops.vpa.minAllowed.cpu }}
memory: {{ .Values.devops.vpa.minAllowed.memory }}
{{- end }}
{{- if .Values.devops.vpa.maxAllowed.enabled }}
maxAllowed:
cpu: {{ .Values.devops.vpa.maxAllowed.cpu }}
memory: {{ .Values.devops.vpa.maxAllowed.memory }}
{{- end }}
{{- if .Values.devops.vpa.controlledResources }}
controlledResources:
{{- range .Values.devops.vpa.controlledResources }}
- {{ . }}
{{- end }}
{{- end }}
{{- end }}

View File

@ -0,0 +1,86 @@
global:
registry: docker.io
repository: freeleaps
nodeSelector: {}
devops:
replicas: 1
image:
registry:
repository: freeleaps
name: devops
tag: 1.0.0
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 8014
protocol: TCP
resources:
requests:
cpu: "0.1"
memory: "64Mi"
limits:
cpu: "0.2"
memory: "128Mi"
# FIXME: Wait until the developers implements the probes APIs
probes: {}
services:
- name: devops-service
type: ClusterIP
port: 8014
targetPort: 8014
serviceMonitor:
enabled: false
labels:
release: kube-prometheus-stack
namespace: freeleaps-monitoring-system
interval: 30s
scrapeTimeout: ""
# Defaults to {}, which means doesn't have any ingress
ingresses: {}
configs:
# 基础配置
tz: "UTC"
appName: "devops"
# JWT 配置
jwtSecretKey: ""
jwtAlgorithm: "HS256"
accessTokenExpireMinutes: "3600"
refreshTokenExpireDays: "1"
# MongoDB 配置
mongodbName: ""
mongodbPort: "27017"
mongodbUri: ""
# 功能开关
metricsEnabled: "false"
probesEnabled: "true"
# 外部服务 URL
baseGiteaUrl: "https://gitea.freeleaps.mathmast.com"
baseReconcileUrl: "https://reconcile.freeleaps.mathmast.com"
baseLokiUrl: "http://loki-gateway.freeleaps-logging-system"
# 日志配置
logBasePath: "/app/log"
logRetention: "30 days"
logRotation: "00:00"
logBackupFiles: "5"
logRotationBytes: "10485760"
# Mock 模式配置
mockMode: "false"
mockResponseDelay: "1000"
vpa:
minAllowed:
enabled: false
cpu: 100m
memory: 64Mi
maxAllowed:
enabled: true
cpu: 100m
memory: 128Mi
controlledResources:
- cpu
- memory

View File

@ -0,0 +1,89 @@
global:
registry: docker.io
repository: freeleaps
nodeSelector: {}
dashboard:
enabled: true
name: freeleaps-prod-authentication-dashboard
title: Authentication Service Dashboard (PROD)
metricsPrefix: freeleaps_authentication
authentication:
replicas: 1
image:
registry: docker.io
repository: null
name: authentication
tag: snapshot-40e0faf
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 8004
protocol: TCP
resources:
requests:
cpu: 200m
memory: 64Mi
limits:
cpu: 300m
memory: 128Mi
probes:
readiness:
type: httpGet
config:
path: /api/_/readyz
port: 8004
initialDelaySeconds: 5
periodSeconds: 30
timeoutSeconds: 3
successThreshold: 1
failureThreshold: 3
liveness:
type: httpGet
config:
path: /api/_/livez
port: 8004
initialDelaySeconds: 5
periodSeconds: 15
timeoutSeconds: 3
successThreshold: 1
failureThreshold: 3
terminationGracePeriodSeconds: 30
services:
- name: authentication-service
type: ClusterIP
port: 8004
targetPort: 8004
serviceMonitor:
enabled: true
labels:
release: kube-prometheus-stack
namespace: freeleaps-monitoring-system
interval: 30s
scrapeTimeout: ''
ingresses: {}
configs:
tz: UTC
appName: authentication
devsvcWebapiUrlBase: http://devsvc-service.freeleaps-prod.svc.freeleaps.cluster:8007/api/devsvc/
notificationWebapiUrlBase: http://notification-service.freeleaps-prod.svc.freeleaps.cluster:8003/api/notification/
jwtSecretKey: ea84edf152976b2fcec12b78aa8e45bc26a5cf0ef61bf16f5c317ae33b3fd8b0
jwtAlgorithm: HS256
serviceApiAccessHost: 0.0.0.0
serviceApiAccessPort: 8004
mongodbName: freeleaps2
mongodbPort: 27017
mongodbUri: mongodb+srv://freeadmin:0eMV0bt8oyaknA0m@freeleaps2.zmsmpos.mongodb.net/?retryWrites=true&w=majority
metricsEnabled: 'true'
probesEnabled: 'true'
vpa:
minAllowed:
enabled: true
cpu: 50m
memory: 64Mi
maxAllowed:
enabled: true
cpu: 200m
memory: 128Mi
controlledResources:
- cpu
- memory

View File

@ -0,0 +1,86 @@
global:
registry: docker.io
repository: freeleaps
nodeSelector: {}
devops:
replicas: 1
image:
registry:
repository: freeleaps
name: devops
tag: 1.0.0
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 8014
protocol: TCP
resources:
requests:
cpu: "0.1"
memory: "64Mi"
limits:
cpu: "0.2"
memory: "128Mi"
# FIXME: Wait until the developers implements the probes APIs
probes: {}
services:
- name: devops-service
type: ClusterIP
port: 8014
targetPort: 8014
serviceMonitor:
enabled: false
labels:
release: kube-prometheus-stack
namespace: freeleaps-monitoring-system
interval: 30s
scrapeTimeout: ""
# Defaults to {}, which means doesn't have any ingress
ingresses: {}
configs:
# 基础配置
tz: "UTC"
appName: "devops"
# JWT 配置
jwtSecretKey: ""
jwtAlgorithm: "HS256"
accessTokenExpireMinutes: "3600"
refreshTokenExpireDays: "1"
# MongoDB 配置
mongodbName: ""
mongodbPort: "27017"
mongodbUri: ""
# 功能开关
metricsEnabled: "false"
probesEnabled: "true"
# 外部服务 URL
baseGiteaUrl: "https://gitea.freeleaps.mathmast.com"
baseReconcileUrl: "https://reconcile.freeleaps.mathmast.com"
baseLokiUrl: "http://loki-gateway.freeleaps-logging-system"
# 日志配置
logBasePath: "/app/log"
logRetention: "30 days"
logRotation: "00:00"
logBackupFiles: "5"
logRotationBytes: "10485760"
# Mock 模式配置
mockMode: "false"
mockResponseDelay: "1000"
vpa:
minAllowed:
enabled: false
cpu: 100m
memory: 64Mi
maxAllowed:
enabled: true
cpu: 100m
memory: 128Mi
controlledResources:
- cpu
- memory