chore: remove all Chinese text, full English internationalization for gitea-webhook-ambassador-python
This commit is contained in:
parent
5b93048cb3
commit
d03c119322
@ -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,
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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"}
|
||||
@ -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')
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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"> </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>
|
||||
|
||||
@ -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();
|
||||
|
||||
140
freeleaps-service-hub/alpha/ci/Jenkinsfile
vendored
140
freeleaps-service-hub/alpha/ci/Jenkinsfile
vendored
@ -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
|
||||
]
|
||||
]
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
|
||||
@ -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
|
||||
]
|
||||
]
|
||||
}
|
||||
6
freeleaps/helm-pkg/devops/Chart.yaml
Normal file
6
freeleaps/helm-pkg/devops/Chart.yaml
Normal 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"
|
||||
@ -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 }}
|
||||
@ -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 }}
|
||||
@ -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 }}
|
||||
@ -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 }}
|
||||
@ -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 }}
|
||||
@ -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 }}
|
||||
32
freeleaps/helm-pkg/devops/templates/authentication/vpa.yaml
Normal file
32
freeleaps/helm-pkg/devops/templates/authentication/vpa.yaml
Normal 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 }}
|
||||
86
freeleaps/helm-pkg/devops/values.alpha.yaml
Normal file
86
freeleaps/helm-pkg/devops/values.alpha.yaml
Normal 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
|
||||
89
freeleaps/helm-pkg/devops/values.prod.yaml
Normal file
89
freeleaps/helm-pkg/devops/values.prod.yaml
Normal 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
|
||||
86
freeleaps/helm-pkg/devops/values.yaml
Normal file
86
freeleaps/helm-pkg/devops/values.yaml
Normal 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
|
||||
Loading…
Reference in New Issue
Block a user