chore(cleanup): 移除 legacy 5888 測試入口
All checks were successful
CD Pipeline / deploy (push) Successful in 1m36s
All checks were successful
CD Pipeline / deploy (push) Successful in 1m36s
This commit is contained in:
@@ -72,7 +72,7 @@ pip install -r requirements.txt
|
|||||||
└── 當日業績/
|
└── 當日業績/
|
||||||
```
|
```
|
||||||
|
|
||||||
2. 開啟網頁介面:http://localhost:5888/auto_import
|
2. 開啟網頁介面:http://localhost/auto_import
|
||||||
|
|
||||||
3. 設定:
|
3. 設定:
|
||||||
- **Google Drive 資料夾路徑**:`業績報表/當日業績`
|
- **Google Drive 資料夾路徑**:`業績報表/當日業績`
|
||||||
@@ -118,7 +118,7 @@ python3 test_google_drive.py
|
|||||||
|
|
||||||
### 監控進度
|
### 監控進度
|
||||||
|
|
||||||
開啟:http://localhost:5888/auto_import
|
開啟:http://localhost/auto_import
|
||||||
|
|
||||||
可以看到:
|
可以看到:
|
||||||
- ✅ 匯入配置
|
- ✅ 匯入配置
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
> 本文件定義專案開發的核心準則與不可違反的規範
|
> 本文件定義專案開發的核心準則與不可違反的規範
|
||||||
> **建立日期**: 2026-01-12
|
> **建立日期**: 2026-01-12
|
||||||
> **當前版本**: V10.21 (模組化治理守門版)
|
> **當前版本**: V10.22 (Legacy 5888 入口清理版)
|
||||||
> **最後更新**: 2026-04-30
|
> **最後更新**: 2026-04-30
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ drive_service.authenticate()
|
|||||||
|
|
||||||
### 5.2 在系統中配置路徑
|
### 5.2 在系統中配置路徑
|
||||||
|
|
||||||
1. 開啟瀏覽器,前往:http://localhost:5888/auto_import
|
1. 開啟瀏覽器,前往:http://localhost/auto_import
|
||||||
2. 在「匯入配置」區域設定:
|
2. 在「匯入配置」區域設定:
|
||||||
- **Google Drive 資料夾路徑**:`業績報表/當日業績`
|
- **Google Drive 資料夾路徑**:`業績報表/當日業績`
|
||||||
- **檔案名稱模式**:`即時業績_當日`(選填,用於過濾檔案)
|
- **檔案名稱模式**:`即時業績_當日`(選填,用於過濾檔案)
|
||||||
@@ -268,7 +268,7 @@ tail -f logs/system.log | grep AutoImport
|
|||||||
|
|
||||||
### 查看匯入統計
|
### 查看匯入統計
|
||||||
|
|
||||||
前往網頁介面:http://localhost:5888/auto_import
|
前往網頁介面:http://localhost/auto_import
|
||||||
|
|
||||||
### 查詢排程統計
|
### 查詢排程統計
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
- AI metrics baseline 觀測:`/metrics` 在尚無 AI 自動化事件時仍輸出 `momo_ai_*` zero-baseline series,避免重啟後 Grafana/Prometheus 看不到 metric names。
|
- AI metrics baseline 觀測:`/metrics` 在尚無 AI 自動化事件時仍輸出 `momo_ai_*` zero-baseline series,避免重啟後 Grafana/Prometheus 看不到 metric names。
|
||||||
- ElephantAlpha transient fallback:NVIDIA NIM timeout、connection error、429 與 5xx 會嘗試下一個 fallback model;400 等非暫時性請求錯誤不重試。
|
- ElephantAlpha transient fallback:NVIDIA NIM timeout、connection error、429 與 5xx 會嘗試下一個 fallback model;400 等非暫時性請求錯誤不重試。
|
||||||
- 模組化治理守門:新增 `docs/guides/modularization_governance.md`、`docs/memory/code_modularization_inventory_20260430.md` 與 `tests/test_modularization_governance.py`,盤點並鎖住 15 個 >800 行 Python 大檔。
|
- 模組化治理守門:新增 `docs/guides/modularization_governance.md`、`docs/memory/code_modularization_inventory_20260430.md` 與 `tests/test_modularization_governance.py`,盤點並鎖住 15 個 >800 行 Python 大檔。
|
||||||
|
- Legacy 5888 入口清理:刪除 `tests/main_test.py` standalone Flask 死碼,測試與自動匯入文件改用 Port 80 入口。
|
||||||
|
|
||||||
【下次待辦】
|
【下次待辦】
|
||||||
- 依 inventory 優先拆 `routes/openclaw_bot_routes.py`、`routes/sales_routes.py`、`scheduler.py`。
|
- 依 inventory 優先拆 `routes/openclaw_bot_routes.py`、`routes/sales_routes.py`、`scheduler.py`。
|
||||||
|
|||||||
6
app.py
6
app.py
@@ -26,7 +26,7 @@ sys.path.insert(0, BASE_DIR)
|
|||||||
|
|
||||||
# 自動檢核並建立必要目錄
|
# 自動檢核並建立必要目錄
|
||||||
try:
|
try:
|
||||||
for folder in ['database', 'services', 'crawler', 'logs', 'data', 'web/templates', 'web/static']:
|
for folder in ['database', 'services', 'crawler', 'logs', 'data', 'templates', 'web/static']:
|
||||||
folder_path = os.path.join(BASE_DIR, folder)
|
folder_path = os.path.join(BASE_DIR, folder)
|
||||||
if not os.path.exists(folder_path):
|
if not os.path.exists(folder_path):
|
||||||
os.makedirs(folder_path)
|
os.makedirs(folder_path)
|
||||||
@@ -95,8 +95,8 @@ except Exception as e:
|
|||||||
sys_log.error(f"無法檢測磁碟空間: {e}")
|
sys_log.error(f"無法檢測磁碟空間: {e}")
|
||||||
|
|
||||||
# 🚩 系統版本定義 (備份與顯示用)
|
# 🚩 系統版本定義 (備份與顯示用)
|
||||||
# 🚩 2026-04-30 V10.21: Modularization governance guardrail
|
# 🚩 2026-04-30 V10.22: Legacy standalone test and port cleanup
|
||||||
SYSTEM_VERSION = "V10.21"
|
SYSTEM_VERSION = "V10.22"
|
||||||
|
|
||||||
# ==========================================
|
# ==========================================
|
||||||
# 🔒 SQL Injection 防護函數
|
# 🔒 SQL Injection 防護函數
|
||||||
|
|||||||
@@ -254,7 +254,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '')
|
|||||||
# ==========================================
|
# ==========================================
|
||||||
# 系統版本與路徑
|
# 系統版本與路徑
|
||||||
# ==========================================
|
# ==========================================
|
||||||
SYSTEM_VERSION = "V10.21"
|
SYSTEM_VERSION = "V10.22"
|
||||||
LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log')
|
LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log')
|
||||||
public_url = PUBLIC_URL # 用於模板顯示
|
public_url = PUBLIC_URL # 用於模板顯示
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
> **最後更新**: 2026-04-30 (台北時間)
|
> **最後更新**: 2026-04-30 (台北時間)
|
||||||
> **狀態**: 🟢 四 AI Agent 自動化閉環已落地 — EventRouter / AutoHeal / OpenClaw Memory / ElephantAlpha bridge / Prometheus metrics / Smoke Dashboard / Smoke Trend Management / Telegram Summary / Grafana provisioning / Prometheus scrape / CD Gunicorn 掛載具測試覆蓋
|
> **狀態**: 🟢 四 AI Agent 自動化閉環已落地 — EventRouter / AutoHeal / OpenClaw Memory / ElephantAlpha bridge / Prometheus metrics / Smoke Dashboard / Smoke Trend Management / Telegram Summary / Grafana provisioning / Prometheus scrape / CD Gunicorn 掛載具測試覆蓋
|
||||||
> **適用版本**: V10.20 ElephantAlpha transient fallback 版
|
> **適用版本**: V10.22 Legacy 5888 入口清理版
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,7 @@
|
|||||||
- **AI metrics baseline 觀測**: `/metrics` 在尚無 AI 自動化事件時仍輸出 `momo_ai_*` zero-baseline series,避免 app 重啟後 Grafana/Prometheus 看不到 metric names。
|
- **AI metrics baseline 觀測**: `/metrics` 在尚無 AI 自動化事件時仍輸出 `momo_ai_*` zero-baseline series,避免 app 重啟後 Grafana/Prometheus 看不到 metric names。
|
||||||
- **ElephantAlpha transient fallback**: NVIDIA NIM primary model timeout、connection error、429 與 5xx 會嘗試下一個 fallback model,400 等非暫時性請求錯誤不重試。
|
- **ElephantAlpha transient fallback**: NVIDIA NIM primary model timeout、connection error、429 與 5xx 會嘗試下一個 fallback model,400 等非暫時性請求錯誤不重試。
|
||||||
- **模組化治理守門**: 盤點 15 個超過 800 行 Python 大檔,新增 `docs/guides/modularization_governance.md` 與 `tests/test_modularization_governance.py`,防止未分類巨檔再長出來。
|
- **模組化治理守門**: 盤點 15 個超過 800 行 Python 大檔,新增 `docs/guides/modularization_governance.md` 與 `tests/test_modularization_governance.py`,防止未分類巨檔再長出來。
|
||||||
|
- **Legacy 5888 入口清理**: 刪除 `tests/main_test.py` standalone Flask 死碼,測試與自動匯入文件改用 Port 80 `/auto_import` 入口。
|
||||||
|
|
||||||
### 2026-04-28~29:Phase 3e 重構大戰 + daily_sales cache 隱形 bug 根除
|
### 2026-04-28~29:Phase 3e 重構大戰 + daily_sales cache 隱形 bug 根除
|
||||||
- **app.py 縮減 -10.8%**: 7,386 → 6,590 行,11 commits 全綠零 502。
|
- **app.py 縮減 -10.8%**: 7,386 → 6,590 行,11 commits 全綠零 502。
|
||||||
|
|||||||
@@ -1,124 +0,0 @@
|
|||||||
import os
|
|
||||||
from flask import Flask, render_template, session, redirect, url_for, send_from_directory
|
|
||||||
from database.manager import DatabaseManager
|
|
||||||
from auth import init_auth_routes, login_required
|
|
||||||
from config import SECRET_KEY, DATA_DIR, LOG_DIR, EXCEL_EXPORT_DIR
|
|
||||||
|
|
||||||
# --- 1. 初始化路徑與 Flask ---
|
|
||||||
BASE_DIR = os.path.abspath(os.path.dirname(__file__))
|
|
||||||
TEMPLATE_DIR = os.path.join(BASE_DIR, 'web', 'templates')
|
|
||||||
STATIC_DIR = os.path.join(BASE_DIR, 'web', 'static')
|
|
||||||
|
|
||||||
app = Flask(__name__,
|
|
||||||
template_folder=TEMPLATE_DIR,
|
|
||||||
static_folder=STATIC_DIR)
|
|
||||||
|
|
||||||
app.secret_key = SECRET_KEY
|
|
||||||
|
|
||||||
# 初始化權限路由
|
|
||||||
init_auth_routes(app)
|
|
||||||
|
|
||||||
# 初始化資料庫管理員
|
|
||||||
db_mgr = DatabaseManager()
|
|
||||||
|
|
||||||
def get_db_size():
|
|
||||||
"""獲取資料庫實體檔案大小"""
|
|
||||||
db_path = os.path.join(DATA_DIR, 'momo_database.db')
|
|
||||||
if os.path.exists(db_path):
|
|
||||||
size_bytes = os.path.getsize(db_path)
|
|
||||||
return f"{size_bytes / (1024 * 1024):.2f} MB"
|
|
||||||
return "0.00 MB"
|
|
||||||
|
|
||||||
# --- 2. 路由定義 ---
|
|
||||||
|
|
||||||
@app.route('/')
|
|
||||||
@login_required
|
|
||||||
def dashboard():
|
|
||||||
"""主看板:包含統計數據與商品明細清單"""
|
|
||||||
stats = {
|
|
||||||
"total_products": 0,
|
|
||||||
"price_changes": 0,
|
|
||||||
"db_size": get_db_size()
|
|
||||||
}
|
|
||||||
recent_products = []
|
|
||||||
|
|
||||||
session_db = db_mgr.Session()
|
|
||||||
try:
|
|
||||||
from database.models import Product, PriceRecord
|
|
||||||
from datetime import datetime
|
|
||||||
from sqlalchemy import desc
|
|
||||||
|
|
||||||
# A. 計算統計數據
|
|
||||||
stats["total_products"] = session_db.query(Product).count()
|
|
||||||
|
|
||||||
today_start = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
|
|
||||||
stats["price_changes"] = session_db.query(PriceRecord).filter(
|
|
||||||
PriceRecord.timestamp >= today_start
|
|
||||||
).count()
|
|
||||||
|
|
||||||
# B. 抓取最新 50 筆商品明細
|
|
||||||
# 邏輯:抓取 Product,並針對每個 Product 找出最後一筆價格
|
|
||||||
products_query = session_db.query(Product).order_by(Product.id.desc()).limit(50).all()
|
|
||||||
|
|
||||||
for p in products_query:
|
|
||||||
# 取得該商品在 PriceRecord 表中最新的一筆紀錄
|
|
||||||
latest_price_rec = session_db.query(PriceRecord)\
|
|
||||||
.filter_by(product_id=p.id)\
|
|
||||||
.order_by(desc(PriceRecord.timestamp))\
|
|
||||||
.first()
|
|
||||||
|
|
||||||
recent_products.append({
|
|
||||||
"category": p.category,
|
|
||||||
"name": p.name,
|
|
||||||
"price": f"{latest_price_rec.price:,.0f}" if latest_price_rec else "N/A",
|
|
||||||
"url": p.url,
|
|
||||||
"time": latest_price_rec.timestamp.strftime('%m/%d %H:%M') if latest_price_rec else "-"
|
|
||||||
})
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
app.logger.error(f"❌ 看板數據讀取失敗: {e}")
|
|
||||||
finally:
|
|
||||||
session_db.close()
|
|
||||||
|
|
||||||
return render_template('dashboard.html', stats=stats, products=recent_products)
|
|
||||||
|
|
||||||
@app.route('/logs')
|
|
||||||
@login_required
|
|
||||||
def view_logs():
|
|
||||||
"""日誌查看頁面"""
|
|
||||||
log_path = os.path.join(LOG_DIR, 'system.log')
|
|
||||||
content = "⚠️ 尚無日誌紀錄。"
|
|
||||||
if os.path.exists(log_path):
|
|
||||||
try:
|
|
||||||
with open(log_path, 'r', encoding='utf-8') as f:
|
|
||||||
f.seek(0, os.SEEK_END)
|
|
||||||
size = f.tell()
|
|
||||||
f.seek(max(0, size - 15000)) # 讀取最後 1.5 萬字元
|
|
||||||
content = f.read()
|
|
||||||
except Exception as e:
|
|
||||||
content = f"讀取日誌出錯: {e}"
|
|
||||||
return render_template('logs.html', log_content=content)
|
|
||||||
|
|
||||||
@app.route('/download/<path:filename>')
|
|
||||||
@login_required
|
|
||||||
def download_excel(filename):
|
|
||||||
"""Excel 報表下載"""
|
|
||||||
return send_from_directory(EXCEL_EXPORT_DIR, filename)
|
|
||||||
|
|
||||||
# --- 3. 啟動自我檢查 ---
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
print("\n" + "="*50)
|
|
||||||
print("🚀 MOMO 伺服器啟動中...")
|
|
||||||
print(f"📂 模板目錄: {TEMPLATE_DIR}")
|
|
||||||
|
|
||||||
# 檢查核心檔案
|
|
||||||
for f in ['dashboard.html', 'login.html']:
|
|
||||||
path = os.path.join(TEMPLATE_DIR, f)
|
|
||||||
status = "✅ 存在" if os.path.exists(path) else "❌ 缺失"
|
|
||||||
size = f"({os.path.getsize(path)} bytes)" if os.path.exists(path) else ""
|
|
||||||
print(f" - {f}: {status} {size}")
|
|
||||||
|
|
||||||
print("="*50 + "\n")
|
|
||||||
|
|
||||||
app.run(host='0.0.0.0', port=5888, debug=True)
|
|
||||||
@@ -90,7 +90,7 @@ def main():
|
|||||||
print("=" * 60)
|
print("=" * 60)
|
||||||
print()
|
print()
|
||||||
print("🌐 開啟網頁介面查看詳細資訊:")
|
print("🌐 開啟網頁介面查看詳細資訊:")
|
||||||
print(" http://localhost:5888/auto_import")
|
print(" http://localhost/auto_import")
|
||||||
print()
|
print()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ try:
|
|||||||
print("Token 已儲存至: config/google_token.pickle")
|
print("Token 已儲存至: config/google_token.pickle")
|
||||||
print()
|
print()
|
||||||
print("現在可以:")
|
print("現在可以:")
|
||||||
print(" 1. 在網頁介面測試連接: http://localhost:5888/auto_import")
|
print(" 1. 在網頁介面測試連接: http://localhost/auto_import")
|
||||||
print(" 2. 執行測試腳本: python3 test_google_drive.py")
|
print(" 2. 執行測試腳本: python3 test_google_drive.py")
|
||||||
print()
|
print()
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -84,11 +84,38 @@ def test_tracked_backup_artifacts_stay_removed():
|
|||||||
"app.py.backup_login_required",
|
"app.py.backup_login_required",
|
||||||
"vendor_stockout_list.html.backup",
|
"vendor_stockout_list.html.backup",
|
||||||
"database/edm_dashboard.html",
|
"database/edm_dashboard.html",
|
||||||
|
"tests/main_test.py",
|
||||||
]
|
]
|
||||||
|
|
||||||
assert [path for path in forbidden_artifacts if (ROOT / path).exists()] == []
|
assert [path for path in forbidden_artifacts if (ROOT / path).exists()] == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_active_code_no_longer_references_legacy_5888_port():
|
||||||
|
active_paths = [
|
||||||
|
ROOT / "app.py",
|
||||||
|
ROOT / "tests",
|
||||||
|
ROOT / "AUTO_IMPORT_README.md",
|
||||||
|
ROOT / "GOOGLE_DRIVE_SETUP.md",
|
||||||
|
]
|
||||||
|
|
||||||
|
offenders = []
|
||||||
|
for active_path in active_paths:
|
||||||
|
paths = active_path.rglob("*") if active_path.is_dir() else [active_path]
|
||||||
|
for path in paths:
|
||||||
|
if (
|
||||||
|
not path.is_file()
|
||||||
|
or path == Path(__file__).resolve()
|
||||||
|
or "__pycache__" in path.parts
|
||||||
|
or path.suffix == ".pyc"
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
content = path.read_text(encoding="utf-8", errors="ignore")
|
||||||
|
if "5888" in content:
|
||||||
|
offenders.append(str(path.relative_to(ROOT)))
|
||||||
|
|
||||||
|
assert offenders == []
|
||||||
|
|
||||||
|
|
||||||
def test_executable_scripts_do_not_use_remove_orphans():
|
def test_executable_scripts_do_not_use_remove_orphans():
|
||||||
script_paths = [
|
script_paths = [
|
||||||
ROOT / "scripts",
|
ROOT / "scripts",
|
||||||
|
|||||||
Reference in New Issue
Block a user