From dedb7bfc57055e14aa8d117f525eb3b51e8f688a Mon Sep 17 00:00:00 2001 From: yumoqing Date: Thu, 27 Aug 2026 11:07:00 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20foms=20=E6=94=B9=E4=B8=BA=E7=BA=AF?= =?UTF-8?q?=E4=B8=9A=E5=8A=A1=E6=A8=A1=E5=9D=97=EF=BC=8C=E5=BA=94=E7=94=A8?= =?UTF-8?q?=E5=B1=82=E8=BF=81=E8=87=B3=E7=BA=AF=E4=BC=9E=E4=BB=93=20foms?= =?UTF-8?q?=5Fapp?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit foms 只保留业务逻辑: · foms/ 包(init.py + failover.py 切换编排器) · models/(5 张表)、json/(CRUD 定义)、wwwroot/api/(端点) · scripts/(foms-agent 及部署脚本) · docs/deploy-operations.md 应用层迁出至 foms_app 纯伞仓: · app/(入口)、build.sh、start/stop.sh、conf/、init/data.yaml · scripts/init_rbac.py / load_path.py(重写为新 URL 布局) · wwwroot/index.ui(重写,指向各模块前缀路径) 同时修复 foms/__init__.py:移除对拆分时已删除函数的导出。 --- .gitignore | 12 +-- README.md | 76 +++++++------------ app/foms.py | 38 ---------- app/global_func.py | 12 --- build.sh | 78 -------------------- conf/config.json | 42 ----------- foms/__init__.py | 24 ++++-- init/data.yaml | 171 ------------------------------------------- scripts/init_rbac.py | 155 --------------------------------------- scripts/load_path.py | 97 ------------------------ start.sh | 6 -- stop.sh | 9 --- wwwroot/index.ui | 87 ---------------------- 13 files changed, 49 insertions(+), 758 deletions(-) delete mode 100644 app/foms.py delete mode 100644 app/global_func.py delete mode 100755 build.sh delete mode 100644 conf/config.json delete mode 100644 init/data.yaml delete mode 100644 scripts/init_rbac.py delete mode 100644 scripts/load_path.py delete mode 100755 start.sh delete mode 100755 stop.sh delete mode 100644 wwwroot/index.ui diff --git a/.gitignore b/.gitignore index 05e4cdb..c2bdafb 100644 --- a/.gitignore +++ b/.gitignore @@ -7,14 +7,6 @@ dist/ *.pid logs/ files/ -pkgs/ -bricks/ -py3/ +models/mysql.ddl.sql +.DS_Store .env -*.swp -*.swo -# CRUD-generated directories (xls2ui) -wwwroot/clusters_list/ -wwwroot/nodes_list/ -wwwroot/sync_databases_list/ -wwwroot/sync_directories_list/ diff --git a/README.md b/README.md index 6cdeb34..1584360 100644 --- a/README.md +++ b/README.md @@ -1,61 +1,41 @@ -# FOMS — Failover Management System +# foms — 主备切换核心模块 -主备机切换管理系统。运行在管理中心 C 上,管理主机 A 和备机 B 之间的: +FOMS 的**领域核心**(业务逻辑层),独立可安装模块。 +应用壳(构建/配置/启动/首页)在纯伞仓 **foms_app**。 -- **MySQL 主从复制** — 实时数据同步 -- **文件目录同步** — rsync 定期同步 -- **健康监控** — 节点心跳 + 复制状态 -- **故障切换** — 自动检测 A 故障 → 切换到 B -- **故障切回** — A 修复后 → 切回 A +## 内容 -## 架构 +| 部分 | 文件 | 说明 | +|---|---|---| +| 包入口 | `foms/init.py` | 集群/节点 CRUD、心跳、仪表盘、远程命令队列、`load_foms()` | +| 切换编排器 | `foms/failover.py` | 六步状态机 + 逆向回滚 + 防脑裂 + 健康判定器 | +| 表定义 | `models/*.json` | clusters、nodes、switchover_logs、switchover_steps、node_commands | +| CRUD 定义 | `json/*.json` | clusters_list、nodes_list | +| 端点 | `wwwroot/api/*.dspy` | 心跳、命令、切换、仪表盘等 | +| Agent | `scripts/foms-agent.py` | 被管主机守护进程(独立部署,非本包代码) | +| 运维文档 | `docs/deploy-operations.md` | 部署 + 运维手册 | +## 切换闭环(2026-08-26 补全) + +健康判定器(防抖告警,半自动)→ 人工确认 → 六步状态机: ``` -管理中心 C (FOMS Web App) - │ - ├── 主机 A ←──MySQL复制/rsync──→ 备机 B - │ - └── Agent 守护进程 (部署在 A & B) +precheck → fence → promote → flip_sync → verify → converge ``` +失败逆向回滚;防脑裂不变量:fence 先于 promote,fence 失败硬中止。 +详见 `docs/deploy-operations.md`。 -## 快速开始 +## 安装 ```bash -# 1. 构建 -./build.sh - -# 2. 配置 .env -vim .env - -# 3. 初始化数据库 (手动执行 models/mysql.ddl.sql) -mysql -u root < models/mysql.ddl.sql - -# 4. 注册RBAC权限 -cd py3/bin && python ../../scripts/load_path.py - -# 5. 启动 -./start.sh +pip install -e . ``` +由 foms_app 的 build.sh 统一组装。`load_foms()` 时自动注册切换编排器 +并拉起健康判定器后台任务(幂等)。 -## 模块 +## 邻接模块(独立仓库) -| 模块 | 路径 | 说明 | -|------|------|------| -| 集群管理 | /clusters_list | 配置主备集群 | -| 节点管理 | /nodes_list | 注册A/B节点(SSH信息) | -| 数据库同步 | /sync_databases_list | MySQL主从复制配置 | -| 文件同步 | /sync_directories_list | rsync目录同步配置 | +- dbbackup — 数据库备份 + 日志同步(binlog 复制) +- filesync — 运行文件同步(rsync) +- hostwatch — 日志监控 + 日志分析 + 主机指标 -## Agent部署 - -在主机A和备机B上: - -```bash -# 配置环境变量 -export FOMS_MANAGEMENT_URL=http://C的IP:9080 -export FOMS_NODE_ID=<节点ID> -export FOMS_AGENT_TOKEN=<安全令牌> - -# 运行Agent -python3 scripts/foms-agent.py -``` +仪表盘对这三模块的表做同库只读聚合(松耦合)。 diff --git a/app/foms.py b/app/foms.py deleted file mode 100644 index d851cae..0000000 --- a/app/foms.py +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env python3 -"""FOMS — Failover Management System 主入口""" -import os, sys - -# Add root so sibling modules are importable -root_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -sys.path.insert(0, root_dir) - -from bricks_for_python.init import load_pybricks -from ahserver.webapp import webapp -from ahserver.serverenv import ServerEnv - -# Foundation modules -from rbac.init import load_rbac -from appbase.init import load_appbase - -# Business modules(核心 + 三个拆出的独立模块) -from foms.init import load_foms -from dbbackup.init import load_dbbackup -from filesync.init import load_filesync -from hostwatch.init import load_hostwatch - -from app.global_func import set_globalvariable - - -def init(): - set_globalvariable() - load_pybricks() - load_appbase() - load_rbac() - load_foms() # 核心:集群/节点/切换/心跳/远程命令 - load_dbbackup() # 数据库备份 + 日志同步(binlog 复制) - load_filesync() # 运行文件同步(rsync) - load_hostwatch() # 日志监控 + 日志分析 + 主机指标 - - -if __name__ == '__main__': - webapp(init) diff --git a/app/global_func.py b/app/global_func.py deleted file mode 100644 index 02859dd..0000000 --- a/app/global_func.py +++ /dev/null @@ -1,12 +0,0 @@ -"""FOMS global functions — injected into dspy context""" -from ahserver.serverenv import ServerEnv - - -def get_module_dbname(mname): - """All FOMS modules share one database.""" - return 'foms' - - -def set_globalvariable(): - env = ServerEnv() - env.get_module_dbname = get_module_dbname diff --git a/build.sh b/build.sh deleted file mode 100755 index 4b3e58a..0000000 --- a/build.sh +++ /dev/null @@ -1,78 +0,0 @@ -#!/usr/bin/env bash -set -e -cdir=$(pwd) -echo "=== FOMS Build ===" - -# 1. Create venv -[ ! -d py3 ] && python3 -m venv py3 -source py3/bin/activate - -# 2. Install foundation packages -mkdir -p pkgs -for m in apppublic sqlor ahserver bricks-for-python xls2ddl; do - cd $cdir/pkgs - [ ! -d "$m" ] && git clone https://git.opencomputing.cn/yumoqing/$m - cd $m && $cdir/py3/bin/pip install -e . -done - -# 3. Build bricks frontend -cd $cdir/pkgs -[ ! -d "bricks" ] && git clone https://git.opencomputing.cn/yumoqing/bricks -cd bricks/bricks && ./build.sh -ln -sf $cdir/pkgs/bricks/dist $cdir/bricks - -# 4. Install auth modules -for m in appbase rbac; do - cd $cdir/pkgs - [ ! -d "$m" ] && git clone https://git.opencomputing.cn/yumoqing/$m - cd $m && $cdir/py3/bin/pip install -e . -done - -# 5. Install FOMS 核心 -cd $cdir && $cdir/py3/bin/pip install -e . - -# 5.5 Install 业务模块(dbbackup / filesync / hostwatch) -for m in dbbackup filesync hostwatch; do - cd $cdir/pkgs - [ ! -d "$m" ] && git clone https://git.opencomputing.cn/yumoqing/$m - cd $m && $cdir/py3/bin/pip install -e . - # 生成模块 DDL - if [ -d models ]; then - json2ddl mysql models/ > models/mysql.ddl.sql 2>/dev/null || echo " skip ddl($m)" - fi -done - -# 6. Generate FOMS 核心 DDL -cd $cdir -[ -d models ] && json2ddl mysql models/ > $cdir/models/mysql.ddl.sql 2>/dev/null || echo " skip ddl" - -# 7. Generate FOMS 核心 CRUD UI (xls2ui for json/) -cd $cdir -if [ -d json ]; then - cd json - for f in *.json; do - echo " xls2ui foms $f" - xls2ui -m ../models -o ../wwwroot foms "$f" 2>/dev/null || echo " skipped" - done -fi - -# 7.5 生成模块 CRUD + 软链模块 wwwroot → 主 wwwroot/<模块名> -cd $cdir -for m in dbbackup filesync hostwatch; do - md=$cdir/pkgs/$m - # 生成模块 CRUD 页面 - if [ -d "$md/json" ]; then - cd "$md/json" - for f in *.json; do - echo " xls2ui $m $f" - xls2ui -m ../models -o ../wwwroot "$m" "$f" 2>/dev/null || echo " skipped" - done - fi - # 软链模块 wwwroot → 主 wwwroot/<模块名>(URL 前缀 /dbbackup/ 等) - ln -sfn "$md/wwwroot" "$cdir/wwwroot/$m" -done - -# 8. Create runtime dirs -mkdir -p $cdir/logs $cdir/files - -echo "=== FOMS Build Complete ===" diff --git a/conf/config.json b/conf/config.json deleted file mode 100644 index a1dfd60..0000000 --- a/conf/config.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "password_key": "foms_secret_key_change_me", - "logger": { - "name": "foms", - "level": "INFO", - "file": "$[workdir]$/logs/foms.log" - }, - "filesroot": "$[workdir]$/files", - "databases": { - "foms": { - "driver": "aiomysql", - "kwargs": { - "host": "127.0.0.1", - "port": 3306, - "user": "root", - "password": "", - "db": "foms", - "charset": "utf8mb4", - "autocommit": true - } - } - }, - "website": { - "paths": [ - ["$[workdir]$/wwwroot", ""], - ["$[workdir]$/bricks", "/bricks"] - ], - "port": 9080, - "indexes": ["index.ui"], - "session_max_time": 86400, - "session_issue_time": 3600, - "processors": [ - [".dspy", "dspy"], - [".ui", "bui"], - [".tmpl", "tmpl"], - [".js", "staticfile"], - [".css", "staticfile"], - [".png", "staticfile"], - [".svg", "staticfile"] - ] - } -} diff --git a/foms/__init__.py b/foms/__init__.py index 13ad8a8..a7564f1 100644 --- a/foms/__init__.py +++ b/foms/__init__.py @@ -1,12 +1,26 @@ +"""foms — 主备切换核心业务模块(FOMS 的领域核心)。 + +独立可安装模块,由纯伞仓 foms_app 组装。 + +内容: + · 表:clusters、nodes、switchover_logs、switchover_steps、node_commands + · 集群/节点 CRUD、Agent 心跳、仪表盘、远程命令队列 + · 切换编排器(六步状态机 + 逆向回滚 + 防脑裂,见 failover.py) + · 健康判定器(后台循环 + 防抖 + 半自动告警) + · scripts/foms-agent.py:被管主机守护进程(非本包代码,独立部署) + +邻接能力已拆为独立模块(不在本包内): + · dbbackup(数据库备份 + 日志同步)、filesync(文件同步)、hostwatch(日志监控分析) +""" from .init import ( + load_foms, create_cluster, update_cluster, delete_cluster, create_node, update_node, delete_node, - create_sync_db, update_sync_db, delete_sync_db, - create_sync_dir, update_sync_dir, delete_sync_dir, - agent_heartbeat, agent_report_health, + agent_heartbeat, trigger_switchover, trigger_switchback, get_dashboard_stats, - agent_report_metrics, agent_push_logs, - get_node_detail, get_node_metrics_history, exec_remote_command, agent_poll_commands, agent_report_command_result, ) +from .failover import ( + confirm_switchover, run_switchover, health_judge_loop, register_failover, +) diff --git a/init/data.yaml b/init/data.yaml deleted file mode 100644 index 1cff890..0000000 --- a/init/data.yaml +++ /dev/null @@ -1,171 +0,0 @@ -appcodes: -- id: cluster_status - name: 集群状态 - hierarchy_flg: 0 -- id: node_role - name: 节点角色 - hierarchy_flg: 0 -- id: node_status - name: 节点状态 - hierarchy_flg: 0 -- id: sync_status - name: 同步状态 - hierarchy_flg: 0 -- id: sync_result - name: 同步结果 - hierarchy_flg: 0 -- id: switchover_event_type - name: 切换事件类型 - hierarchy_flg: 0 -- id: switchover_trigger - name: 触发方式 - hierarchy_flg: 0 -- id: switchover_status - name: 切换执行状态 - hierarchy_flg: 0 -- id: org_type - name: 机构类型 - hierarchy_flg: 0 -- id: user_status - name: 用户状态 - hierarchy_flg: 0 -- id: log_level - name: 日志级别 - hierarchy_flg: 0 - -appcodes_kv: -- id: cluster_status_running - parentid: cluster_status - k: running - v: 运行中 -- id: cluster_status_stopped - parentid: cluster_status - k: stopped - v: 已停止 -- id: cluster_status_switching - parentid: cluster_status - k: switching - v: 切换中 -- id: cluster_status_error - parentid: cluster_status - k: error - v: 异常 - -- id: node_role_master - parentid: node_role - k: master - v: 主机 -- id: node_role_standby - parentid: node_role - k: standby - v: 备机 - -- id: node_status_online - parentid: node_status - k: online - v: 在线 -- id: node_status_offline - parentid: node_status - k: offline - v: 离线 -- id: node_status_error - parentid: node_status - k: error - v: 异常 - -- id: sync_status_not_configured - parentid: sync_status - k: not_configured - v: 未配置 -- id: sync_status_running - parentid: sync_status - k: running - v: 同步中 -- id: sync_status_error - parentid: sync_status - k: error - v: 异常 -- id: sync_status_failed - parentid: sync_status - k: failed - v: 失败 - -- id: sync_result_success - parentid: sync_result - k: success - v: 成功 -- id: sync_result_failed - parentid: sync_result - k: failed - v: 失败 -- id: sync_result_running - parentid: sync_result - k: running - v: 同步中 - -- id: switchover_event_failover - parentid: switchover_event_type - k: failover - v: 故障切换 -- id: switchover_event_failback - parentid: switchover_event_type - k: failback - v: 故障切回 -- id: switchover_event_manual - parentid: switchover_event_type - k: manual - v: 手动操作 - -- id: switchover_trigger_auto - parentid: switchover_trigger - k: auto - v: 自动检测 -- id: switchover_trigger_manual - parentid: switchover_trigger - k: manual - v: 手动触发 - -- id: switchover_status_pending - parentid: switchover_status - k: pending - v: 等待执行 -- id: switchover_status_in_progress - parentid: switchover_status - k: in_progress - v: 执行中 -- id: switchover_status_success - parentid: switchover_status - k: success - v: 成功 -- id: switchover_status_failed - parentid: switchover_status - k: failed - v: 失败 -- id: org_type_foms - parentid: org_type - k: foms_org - v: FOMS运维组织 -- id: user_status_active - parentid: user_status - k: 0 - v: 正常 -- id: user_status_disabled - parentid: user_status - k: 1 - v: 停用 -- id: log_level_info - parentid: log_level - k: INFO - v: 信息 -- id: log_level_warn - parentid: log_level - k: WARN - v: 警告 -- id: log_level_error - parentid: log_level - k: ERROR - v: 错误 -- id: log_level_crit - parentid: log_level - k: CRIT - v: 严重 diff --git a/scripts/init_rbac.py b/scripts/init_rbac.py deleted file mode 100644 index d1bf9d7..0000000 --- a/scripts/init_rbac.py +++ /dev/null @@ -1,155 +0,0 @@ -#!/usr/bin/env python3 -""" -FOMS RBAC 初始化 — 创建机构类型、机构、角色、用户 - -用法: cd /d/ymq/repos/foms && source py3/bin/activate && python scripts/init_rbac.py -""" -import os, sys, asyncio - -# Add project root -root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -sys.path.insert(0, root) - -from appPublic.uniqueID import getID -from appPublic.timeUtils import curDateString -from sqlor.dbpools import DBPools -from ahserver.serverenv import ServerEnv - -DBNAME = 'foms' - -ORG_ID = 'foms_org_001' -ROLES = { - 'foms_superadmin': '超级管理员', - 'foms_operator': '运维操作员', - 'foms_viewer': '只读观察者', -} -USERS = { - 'admin': {'name': '系统管理员', 'password': 'Admin@123', 'role': 'foms_superadmin'}, - 'operator': {'name': '运维操作员', 'password': 'Ops@123456', 'role': 'foms_operator'}, -} - - -async def main(): - env = ServerEnv() - dbname = env.get_module_dbname('rbac') if hasattr(env, 'get_module_dbname') else DBNAME - db = DBPools() - - async with db.sqlorContext(dbname) as sor: - # 1. Create organization - existing = await sor.R('organization', {'id': ORG_ID}) - if existing: - print(f"[SKIP] organization {ORG_ID} already exists") - else: - await sor.C('organization', { - 'id': ORG_ID, - 'orgname': 'FOMS运维中心', - 'orgabbr': 'FOMS', - 'org_type': 'foms_org', - }) - print(f"[OK] organization created: FOMS运维中心") - - # 2. Create roles - for role_id, role_name in ROLES.items(): - existing = await sor.R('role', {'id': role_id}) - if existing: - print(f"[SKIP] role {role_id} exists") - continue - await sor.C('role', { - 'id': role_id, - 'orgtypeid': 'foms_org', - 'name': role_name, - }) - print(f"[OK] role created: {role_id} ({role_name})") - - # 3. Create users - for username, info in USERS.items(): - uid = f'user_{username}' - existing = await sor.R('users', {'username': username}) - if existing: - print(f"[SKIP] user {username} exists") - else: - encoded_pw = env.password_encode(info['password']) - await sor.C('users', { - 'id': uid, - 'username': username, - 'name': info['name'], - 'nick_name': info['name'], - 'password': encoded_pw, - 'orgid': ORG_ID, - 'user_status': '0', - 'created_at': curDateString(), - }) - print(f"[OK] user created: {username}") - - # 4. Assign role to user - ur_id = f'ur_{username}_{info["role"]}' - existing_ur = await sor.R('userrole', {'userid': uid, 'roleid': info['role']}) - if existing_ur: - print(f"[SKIP] userrole {username}→{info['role']} exists") - continue - await sor.C('userrole', { - 'id': ur_id, - 'userid': uid, - 'roleid': info['role'], - }) - print(f"[OK] userrole: {username} → {info['role']}") - - # 5. Assign permissions to roles - # Get all FOMS-specific permissions (paths not from rbac/appbase) - all_perms = await sor.sqlExe( - "SELECT id, path FROM permission WHERE path IN (" - "SELECT path FROM permission WHERE path LIKE '/api/%' OR path LIKE '/%_list%'" - ") ORDER BY path", {} - ) - # Most paths start with / for FOMS root-mapped module - foms_perms = [p for p in all_perms if hasattr(p, 'path')] - if not foms_perms: - print("[WARN] No FOMS permissions found — run scripts/load_path.py first") - else: - # superadmin: all FOMS paths - # operator: all paths (can trigger switchover) - # viewer: read-only (no create/update/delete) - for perm in foms_perms: - pid = getattr(perm, 'id', '') - path = getattr(perm, 'path', '') - is_write = any(x in path for x in ['create', 'update', 'delete', 'trigger']) - is_agent = 'agent_' in path - - # superadmin gets everything - await _add_roleperm(sor, 'foms_superadmin', pid, path) - - # operator gets everything except agent paths (internal) - if not is_agent: - await _add_roleperm(sor, 'foms_operator', pid, path) - - # viewer gets read-only - if not is_write and not is_agent: - await _add_roleperm(sor, 'foms_viewer', pid, path) - - print(f"\n=== FOMS RBAC 初始化完成 ===") - print(f" 机构: FOMS运维中心 ({ORG_ID})") - print(f" 角色: {', '.join(f'{k}({v})' for k, v in ROLES.items())}") - print(f" 用户:") - for u, i in USERS.items(): - print(f" {u} / {i['password']} (角色: {i['role']})") - print(f"\n 角色权限分配:") - print(f" superadmin → 全部路径") - print(f" operator → 全部路径 (不含agent内部端点)") - print(f" viewer → 只读路径 (查看dashboard/列表)") - - -async def _add_roleperm(sor, role_id, perm_id, path): - """Add rolepermission if not exists""" - rp_id = f'rp_{role_id}_{perm_id}'[:32] - existing = await sor.R('rolepermission', {'roleid': role_id, 'permid': perm_id}) - if existing: - return - await sor.C('rolepermission', { - 'id': rp_id, - 'roleid': role_id, - 'permid': perm_id, - }) - - -if __name__ == '__main__': - asyncio.run(main()) diff --git a/scripts/load_path.py b/scripts/load_path.py deleted file mode 100644 index 1b97e8c..0000000 --- a/scripts/load_path.py +++ /dev/null @@ -1,97 +0,0 @@ -#!/usr/bin/env python3 -"""FOMS RBAC permission registration""" -import os, sys - -# Find Sage root for set_role_perm.py -sage_root = None -for c in [os.path.expanduser("~/repos/sage"), os.path.expanduser("~/test/sage")]: - if os.path.isdir(os.path.join(c, "py3", "bin")): - sage_root = c - break -if not sage_root: - print("ERROR: Sage root not found") - sys.exit(1) - -sys.path.insert(0, os.path.join(sage_root, "py3", "bin")) - -from set_role_perm import set_path_perm - -# Paths accessible without login -PATHS_ANY = [ - "/", - "/index.ui", - "/api/login.dspy", -] - -# Paths requiring authentication -PATHS_LOGINED = [ - "/api/clusters_create.dspy", - "/api/clusters_update.dspy", - "/api/clusters_delete.dspy", - "/api/nodes_create.dspy", - "/api/nodes_update.dspy", - "/api/nodes_delete.dspy", - "/api/sync_databases_create.dspy", - "/api/sync_databases_update.dspy", - "/api/sync_databases_delete.dspy", - "/api/sync_directories_create.dspy", - "/api/sync_directories_update.dspy", - "/api/sync_directories_delete.dspy", - "/api/agent_heartbeat.dspy", - "/api/agent_report_health.dspy", - "/api/trigger_switchover.dspy", - "/api/trigger_switchback.dspy", - "/api/dashboard_stats.dspy", - # CRUD list pages - "/clusters_list", - "/clusters_list/index.ui", - "/nodes_list", - "/nodes_list/index.ui", - "/sync_databases_list", - "/sync_databases_list/index.ui", - "/sync_directories_list", - "/sync_directories_list/index.ui", - # CRUD generated files - "/clusters_list/get_clusters_list.dspy", - "/clusters_list/add_clusters_list.dspy", - "/clusters_list/update_clusters_list.dspy", - "/clusters_list/delete_clusters_list.dspy", - "/nodes_list/get_nodes_list.dspy", - "/nodes_list/add_nodes_list.dspy", - "/nodes_list/update_nodes_list.dspy", - "/nodes_list/delete_nodes_list.dspy", - "/sync_databases_list/get_sync_databases_list.dspy", - "/sync_databases_list/add_sync_databases_list.dspy", - "/sync_databases_list/update_sync_databases_list.dspy", - "/sync_databases_list/delete_sync_databases_list.dspy", - "/sync_directories_list/get_sync_directories_list.dspy", - "/sync_directories_list/add_sync_directories_list.dspy", - "/sync_directories_list/update_sync_directories_list.dspy", - "/sync_directories_list/delete_sync_directories_list.dspy", - # Metrics & logs - "/api/agent_report_metrics.dspy", - "/api/agent_push_logs.dspy", - "/api/node_detail.dspy", - "/api/node_metrics_history.dspy", - # Remote command - "/api/exec_remote_command.dspy", - "/api/agent_poll_commands.dspy", - "/api/agent_report_command_result.dspy", -] - -# Agent heartbeat endpoint: no auth (agents use token) -PATHS_ANY.append("/api/agent_heartbeat.dspy") -PATHS_ANY.append("/api/agent_report_health.dspy") -PATHS_ANY.append("/api/agent_report_metrics.dspy") -PATHS_ANY.append("/api/agent_push_logs.dspy") -PATHS_ANY.append("/api/agent_poll_commands.dspy") -PATHS_ANY.append("/api/agent_report_command_result.dspy") - -if __name__ == '__main__': - for p in PATHS_ANY: - set_path_perm("any", p) - print(f" any {p}") - for p in PATHS_LOGINED: - set_path_perm("logined", p) - print(f" logined {p}") - print("Done. Restart FOMS to apply.") diff --git a/start.sh b/start.sh deleted file mode 100755 index a00aee6..0000000 --- a/start.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash -cd "$(dirname "$0")" -[ -f .env ] && source .env -nohup $PWD/py3/bin/python $PWD/app/foms.py -p ${FOMS_PORT:-9080} -w $PWD > $PWD/logs/foms.out 2>&1 & -echo $! > foms.pid -echo "FOMS started (PID: $(cat foms.pid)) on port ${FOMS_PORT:-9080}" diff --git a/stop.sh b/stop.sh deleted file mode 100755 index 192f6fa..0000000 --- a/stop.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env bash -cd "$(dirname "$0")" -if [ -f foms.pid ]; then - pid=$(cat foms.pid) - kill $pid 2>/dev/null && echo "FOMS stopped (PID: $pid)" || echo "FOMS not running" - rm -f foms.pid -else - echo "No PID file found" -fi diff --git a/wwwroot/index.ui b/wwwroot/index.ui deleted file mode 100644 index 6ba83b4..0000000 --- a/wwwroot/index.ui +++ /dev/null @@ -1,87 +0,0 @@ -{ - "widgettype": "VBox", - "options": {"width": "100%", "height": "100%", "padding": "24px", "backgroundColor": "#F5F7FA"}, - "subwidgets": [ - { - "widgettype": "Text", - "options": {"text": "FOMS — 主备切换管理系统", "fontSize": "28px", "fontWeight": "bold", "marginBottom": "8px"} - }, - { - "widgettype": "Text", - "options": {"text": "Failover Management System · 实时监控 · 自动切换 · 数据同步", "fontSize": "14px", "color": "#666", "marginBottom": "24px"} - }, - { - "widgettype": "ResponsableBox", - "options": {"gap": "16px", "minWidth": "220px", "marginBottom": "24px"}, - "subwidgets": [ - { - "widgettype": "VBox", - "options": {"backgroundColor": "#FFFFFF", "padding": "20px", "borderRadius": "8px", "cursor": "pointer"}, - "binds": [{"wid": "self", "event": "click", "actiontype": "urlwidget", "target": "app.foms_content", "options": {"url": "{{entire_url('clusters_list')}}"}, "mode": "replace"}], - "subwidgets": [ - {"widgettype": "Text", "options": {"text": "集群管理", "fontSize": "16px", "fontWeight": "bold", "marginBottom": "8px"}}, - {"widgettype": "RefreshWidget", "options": {"interval": 30000, "data_url": "{{entire_url('api/dashboard_stats.dspy')}}"}, - "subwidgets": [ - {"widgettype": "Text", "options": {"text": "{{data.total_clusters}} 个集群", "fontSize": "14px", "color": "#666"}} - ] - } - ] - }, - { - "widgettype": "VBox", - "options": {"backgroundColor": "#FFFFFF", "padding": "20px", "borderRadius": "8px", "cursor": "pointer"}, - "binds": [{"wid": "self", "event": "click", "actiontype": "urlwidget", "target": "app.foms_content", "options": {"url": "{{entire_url('nodes_list')}}"}, "mode": "replace"}], - "subwidgets": [ - {"widgettype": "Text", "options": {"text": "节点管理", "fontSize": "16px", "fontWeight": "bold", "marginBottom": "8px"}}, - {"widgettype": "RefreshWidget", "options": {"interval": 30000, "data_url": "{{entire_url('api/dashboard_stats.dspy')}}"}, - "subwidgets": [ - {"widgettype": "Text", "options": {"text": "{{data.nodes_online}} 在线 / {{data.nodes_offline}} 离线", "fontSize": "14px", "color": "#666"}} - ] - } - ] - }, - { - "widgettype": "VBox", - "options": {"backgroundColor": "#FFFFFF", "padding": "20px", "borderRadius": "8px", "cursor": "pointer"}, - "binds": [{"wid": "self", "event": "click", "actiontype": "urlwidget", "target": "app.foms_content", "options": {"url": "{{entire_url('sync_databases_list')}}"}, "mode": "replace"}], - "subwidgets": [ - {"widgettype": "Text", "options": {"text": "数据库同步", "fontSize": "16px", "fontWeight": "bold", "marginBottom": "8px"}}, - {"widgettype": "RefreshWidget", "options": {"interval": 30000, "data_url": "{{entire_url('api/dashboard_stats.dspy')}}"}, - "subwidgets": [ - {"widgettype": "Text", "options": {"text": "{{data.dbs_synced}} 正常 / {{data.dbs_error}} 异常", "fontSize": "14px", "color": "#666"}} - ] - } - ] - }, - { - "widgettype": "VBox", - "options": {"backgroundColor": "#FFFFFF", "padding": "20px", "borderRadius": "8px", "cursor": "pointer"}, - "binds": [{"wid": "self", "event": "click", "actiontype": "urlwidget", "target": "app.foms_content", "options": {"url": "{{entire_url('sync_directories_list')}}"}, "mode": "replace"}], - "subwidgets": [ - {"widgettype": "Text", "options": {"text": "文件同步", "fontSize": "16px", "fontWeight": "bold", "marginBottom": "8px"}}, - {"widgettype": "RefreshWidget", "options": {"interval": 30000, "data_url": "{{entire_url('api/dashboard_stats.dspy')}}"}, - "subwidgets": [ - {"widgettype": "Text", "options": {"text": "{{data.dirs_synced}} 同步正常", "fontSize": "14px", "color": "#666"}} - ] - } - ] - } - ] - }, - { - "widgettype": "VBox", - "id": "foms_content", - "options": {"width": "100%", "flex": "1", "backgroundColor": "#FFFFFF", "borderRadius": "8px", "padding": "20px", "minHeight": "400px"}, - "subwidgets": [ - { - "widgettype": "Text", - "options": {"text": "欢迎使用 FOMS 主备切换管理系统", "fontSize": "18px", "color": "#999", "textAlign": "center", "marginTop": "80px"} - }, - { - "widgettype": "Text", - "options": {"text": "点击上方卡片进入各功能模块", "fontSize": "14px", "color": "#BBB", "textAlign": "center"} - } - ] - } - ] -}