Files
vmis/backend/app/services/scheduler/schedule_system.py
VMIS Developer 62baadb06f feat(vmis): 租戶自動開通完整流程 + Admin Portal SSO + NC 行事曆訂閱
Backend:
- schedule_tenant: NC 新容器自動 pgsql 安裝 (_nc_db_check 全新容器處理)
- schedule_tenant: NC 初始化加入 Redis + APCu memcache 設定 (修正 OIDC invalid_state)
- schedule_tenant: 新租戶 KC realm 自動設定 accessCodeLifespan=600s (修正 authentication_expired)
- schedule_account: NC Mail 帳號自動設定 (nc_mail_result/nc_mail_done_at)
- schedule_account: NC 台灣國定假日行事曆自動訂閱 (CalDAV MKCALENDAR)
- nextcloud_client: 新增 subscribe_calendar() CalDAV 訂閱方法
- settings: 新增系統設定 API (site_title/version/timezone/SSO/Keycloak)
- models/result: 新增 nc_mail_result, nc_mail_done_at 欄位
- alembic: 遷移 002(system_settings) 003(keycloak_admin) 004(nc_mail_result)

Frontend (Admin Portal):
- 新增完整管理後台 (index/tenants/accounts/servers/schedules/logs/settings/system-status)
- api.js: Keycloak JS Adapter SSO 整合 (PKCE/S256, fallback KC JS 來源, 自動 token 更新)
- index.html: Promise.allSettled 取代 Promise.all,防止單一 API 失敗影響整頁
- 所有頁面加入 try/catch + toast 錯誤處理
- 新增品牌 LOGO 與 favicon

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-15 15:31:37 +08:00

96 lines
3.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Schedule 3 — 系統狀態(每日 08:00
Part A: 基礎設施服務功能驗證traefik/keycloak/mail/db
Part B: 伺服器 ping 檢查
"""
import logging
from datetime import datetime
from app.core.utils import now_tw
from sqlalchemy.orm import Session
from app.models.server import SystemStatusLog, ServerStatusLog, Server
logger = logging.getLogger(__name__)
# Fixed 8 services: environment × service_name
SERVICES = [
{"environment": "test", "service_name": "traefik",
"service_desc": "測試環境反向代理", "host": "localhost", "port": 8080},
{"environment": "test", "service_name": "keycloak",
"service_desc": "測試環境 SSO",
"url": "https://auth.lab.taipei", "realm": "master"},
{"environment": "test", "service_name": "mail",
"service_desc": "測試環境 Mail Server", "host": "localhost", "port": 587},
{"environment": "test", "service_name": "db",
"service_desc": "10.1.0.20:5433 PostgreSQL",
"db_host": "10.1.0.20", "db_port": 5433},
{"environment": "prod", "service_name": "traefik",
"service_desc": "正式環境反向代理", "host": "localhost", "port": 8080},
{"environment": "prod", "service_name": "keycloak",
"service_desc": "正式環境 SSO",
"url": "https://auth.ease.taipei", "realm": "master"},
{"environment": "prod", "service_name": "mail",
"service_desc": "正式環境 Mail Server", "host": "10.1.0.254", "port": 587},
{"environment": "prod", "service_name": "db",
"service_desc": "10.1.0.254:5432 PostgreSQL",
"db_host": "10.1.0.254", "db_port": 5432},
]
def run_system_status(schedule_log_id: int, db: Session):
from app.services.system_checker import SystemChecker
checker = SystemChecker()
# Part A: Infrastructure services
for svc in SERVICES:
result = False
fail_reason = None
try:
if svc["service_name"] == "traefik":
result = checker.check_traefik(svc["host"], svc["port"])
elif svc["service_name"] == "keycloak":
result = checker.check_keycloak(svc["url"], svc["realm"])
elif svc["service_name"] == "mail":
result = checker.check_smtp(svc["host"], svc["port"])
elif svc["service_name"] == "db":
result = checker.check_postgres(svc["db_host"], svc["db_port"])
except Exception as e:
result = False
fail_reason = str(e)
db.add(SystemStatusLog(
schedule_log_id=schedule_log_id,
environment=svc["environment"],
service_name=svc["service_name"],
service_desc=svc["service_desc"],
result=result,
fail_reason=fail_reason,
recorded_at=now_tw(),
))
# Part B: Server ping
servers = db.query(Server).filter(Server.is_active == True).order_by(Server.sort_order).all()
for server in servers:
response_time = None
fail_reason = None
try:
response_time = checker.ping_server(server.ip_address)
result = response_time is not None
if not result:
fail_reason = "No response"
except Exception as e:
result = False
fail_reason = str(e)
db.add(ServerStatusLog(
schedule_log_id=schedule_log_id,
server_id=server.id,
result=result,
response_time=response_time,
fail_reason=fail_reason,
recorded_at=now_tw(),
))
db.commit()
logger.info(f"System status check done: {len(SERVICES)} services + {len(servers)} servers")