diff --git a/config.py b/config.py index 453c705..1327858 100644 --- a/config.py +++ b/config.py @@ -320,7 +320,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '') # ========================================== # 系統版本與路徑 # ========================================== -SYSTEM_VERSION = "V10.164" +SYSTEM_VERSION = "V10.165" LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log') public_url = PUBLIC_URL # 用於模板顯示 diff --git a/templates/ewoooc_base.html b/templates/ewoooc_base.html index b88a43f..b4f78f4 100644 --- a/templates/ewoooc_base.html +++ b/templates/ewoooc_base.html @@ -36,301 +36,7 @@ {% block extra_head %}{% endblock %} - + {# MOMO 404 防呆攔截:避免無效商品連結導向 EC404 #} - - - - - + {% if active_page|default('') in [ 'obs_overview', 'obs_agent_orchestration', 'obs_business_intel', diff --git a/web/static/css/ewoooc-bootstrap-overrides.css b/web/static/css/ewoooc-bootstrap-overrides.css new file mode 100644 index 0000000..fa76a2f --- /dev/null +++ b/web/static/css/ewoooc-bootstrap-overrides.css @@ -0,0 +1,294 @@ +/* EwoooC base Bootstrap overrides extracted from templates/ewoooc_base.html. */ + /* ═══════════════════════════════════════════════════ + * Bootstrap override — 限定在 .momo-app 內 + * ═══════════════════════════════════════════════════ */ + + /* 主 accent 統一替換為群組色 */ + .momo-app .btn-primary { + background: var(--momo-page-accent); + border-color: var(--momo-page-accent); + color: var(--momo-page-inverse); + box-shadow: none; + } + .momo-app .btn-primary:hover, + .momo-app .btn-primary:focus { + background: var(--momo-page-accent-dark); + border-color: var(--momo-page-accent-dark); + color: var(--momo-page-inverse); + } + + .momo-app .btn-outline-primary { + color: var(--momo-page-accent-dark); + border-color: var(--momo-page-accent-line); + background: transparent; + } + .momo-app .btn-outline-primary:hover { + color: var(--momo-page-inverse); + background: var(--momo-page-accent); + border-color: var(--momo-page-accent); + } + + .momo-app .text-primary { + color: var(--momo-page-accent-dark) !important; + } + + .momo-app .bg-primary { + background: var(--momo-page-accent) !important; + color: var(--momo-page-inverse) !important; + } + + .momo-app .border-primary { + border-color: var(--momo-page-accent-line) !important; + } + + /* Card 統一風格 — 平面化 */ + .momo-app .card { + background: var(--momo-bg-surface); + border: 1px solid var(--momo-border-light); + border-radius: var(--momo-radius-md); + box-shadow: none; + } + .momo-app .card-header { + background: transparent; + border-bottom: 1px solid var(--momo-border-light); + padding: var(--momo-space-3) var(--momo-space-4); + font-family: var(--momo-font-display); + font-weight: 700; + color: var(--momo-text-primary); + } + .momo-app .card-body { + padding: var(--momo-space-4); + } + + /* Page header — 移除原本黑色/橘色漸層 hero */ + .momo-app .page-header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: var(--momo-space-4); + padding: var(--momo-space-4) 0 var(--momo-space-5); + border-bottom: 1px solid var(--momo-border-light); + margin-bottom: var(--momo-space-5); + background: transparent; + } + .momo-app .page-header h1, + .momo-app .page-header h2, + .momo-app .page-header h3 { + margin: 0; + color: var(--momo-text-primary); + font-family: var(--momo-font-display); + font-size: var(--momo-text-headline); + font-weight: 700; + letter-spacing: 0; + line-height: 1.2; + } + .momo-app .page-header p, + .momo-app .page-header small, + .momo-app .page-header .text-muted { + color: var(--momo-text-secondary); + } + + /* Bootstrap badges → 暖色 tag */ + .momo-app .badge.bg-primary { + background: var(--momo-tag-caramel-bg) !important; + color: var(--momo-tag-caramel-text) !important; + border: 1px solid var(--momo-tag-caramel-border); + font-weight: 600; + } + .momo-app .badge.bg-warning, + .momo-app .text-bg-warning { + background: var(--momo-tag-honey-bg) !important; + color: var(--momo-tag-honey-text) !important; + border: 1px solid var(--momo-tag-honey-border); + } + .momo-app .badge.bg-danger, + .momo-app .text-bg-danger { + background: var(--momo-tag-rust-bg) !important; + color: var(--momo-tag-rust-text) !important; + border: 1px solid var(--momo-tag-rust-border); + } + .momo-app .badge.bg-success, + .momo-app .text-bg-success { + background: var(--momo-tag-success-bg) !important; + color: var(--momo-tag-success-text) !important; + border: 1px solid var(--momo-tag-success-border); + } + .momo-app .badge.bg-info, + .momo-app .text-bg-info { + background: var(--momo-tag-olive-bg) !important; + color: var(--momo-tag-olive-text) !important; + border: 1px solid var(--momo-tag-olive-border); + } + .momo-app .badge.bg-dark, + .momo-app .badge.bg-secondary { + background: var(--momo-tag-ink-bg) !important; + color: var(--momo-tag-ink-text) !important; + border: 1px solid var(--momo-tag-ink-border); + } + .momo-app .badge { + font-family: var(--momo-font-mono); + font-weight: 600; + font-size: var(--momo-text-label); + letter-spacing: 0.02em; + padding: 3px 8px; + border-radius: var(--momo-radius-sm); + } + + /* Table — 平面化 */ + .momo-app .table { + color: var(--momo-text-primary); + margin-bottom: 0; + } + .momo-app .table > :not(caption) > * > * { + background: transparent; + border-bottom-color: var(--momo-border-light); + padding: var(--momo-space-3) var(--momo-space-3); + } + .momo-app .table thead th { + background: var(--momo-bg-paper) !important; + color: var(--momo-text-secondary) !important; + border-bottom: 1px solid var(--momo-border-light); + font-family: var(--momo-font-display); + font-size: var(--momo-text-label); + font-weight: 600; + letter-spacing: 0.06em; + text-transform: uppercase; + white-space: nowrap; + } + .momo-app .table tbody tr:hover { + background: var(--momo-page-accent-soft); + } + + /* Form */ + .momo-app .form-control, + .momo-app .form-select { + background: var(--momo-bg-elevated); + border: 1px solid var(--momo-border-light); + border-radius: var(--momo-radius-md); + color: var(--momo-text-primary); + font-size: var(--momo-text-body-sm); + box-shadow: none; + } + .momo-app .form-control:focus, + .momo-app .form-select:focus { + border-color: var(--momo-page-accent-line); + box-shadow: var(--momo-shadow-focus); + } + + /* Progress bar */ + .momo-app .progress { + background: var(--momo-bg-subtle); + border-radius: var(--momo-radius-pill); + height: 6px; + box-shadow: none; + } + .momo-app .progress-bar { + background: var(--momo-page-accent); + } + + /* Filter section — 移除原本 gradient header */ + .momo-app .filter-section { + padding: var(--momo-space-4); + background: var(--momo-bg-paper); + border: 1px solid var(--momo-border-light); + border-radius: var(--momo-radius-md); + color: var(--momo-text-primary); + } + .momo-app .filter-section h3, + .momo-app .filter-section h4, + .momo-app .filter-section h5 { + color: var(--momo-text-primary); + font-family: var(--momo-font-display); + font-weight: 700; + } + .momo-app .filter-section .form-label { + color: var(--momo-text-secondary); + font-family: var(--momo-font-display); + font-size: var(--momo-text-label); + font-weight: 600; + letter-spacing: 0.06em; + text-transform: uppercase; + } + + /* Nav pills / tabs */ + .momo-app .nav-pills .nav-link.active, + .momo-app .nav-tabs .nav-link.active { + background: var(--momo-page-accent); + color: var(--momo-page-inverse); + border-color: var(--momo-page-accent); + } + .momo-app .nav-pills .nav-link, + .momo-app .nav-tabs .nav-link { + color: var(--momo-text-secondary); + font-weight: 500; + } + + /* Pagination */ + .momo-app .page-item.active .page-link { + background: var(--momo-page-accent); + border-color: var(--momo-page-accent); + color: var(--momo-page-inverse); + } + .momo-app .page-link { + color: var(--momo-text-secondary); + background: var(--momo-bg-elevated); + border-color: var(--momo-border-light); + } + + /* Dropdown */ + .momo-app .dropdown-menu { + background: var(--momo-bg-elevated); + border: 1px solid var(--momo-border-light); + border-radius: var(--momo-radius-md); + box-shadow: var(--momo-shadow-modal); + } + .momo-app .dropdown-item.active, + .momo-app .dropdown-item:active { + background: var(--momo-page-accent); + color: var(--momo-page-inverse); + } + .momo-app .dropdown-item:hover { + background: var(--momo-page-accent-soft); + color: var(--momo-page-accent-dark); + } + + /* Modal */ + .momo-app .modal-content { + background: var(--momo-bg-elevated); + border: 1px solid var(--momo-border-light); + border-radius: var(--momo-radius-xl); + box-shadow: var(--momo-shadow-modal); + } + .momo-app .modal-header { + border-bottom-color: var(--momo-border-light); + } + .momo-app .modal-footer { + border-top-color: var(--momo-border-light); + } + + /* Alert — 去飽和 */ + .momo-app .alert { + border-radius: var(--momo-radius-md); + border-width: 1px; + font-size: var(--momo-text-body-sm); + } + .momo-app .alert-warning { + background: var(--momo-warning-bg); + border-color: var(--momo-warning-border); + color: var(--momo-warning-text); + } + .momo-app .alert-danger { + background: var(--momo-danger-bg); + border-color: var(--momo-danger-border); + color: var(--momo-danger-text); + } + .momo-app .alert-success { + background: var(--momo-success-bg); + border-color: var(--momo-success-border); + color: var(--momo-success-text); + } + .momo-app .alert-info { + background: var(--momo-info-bg); + border-color: var(--momo-info-border); + color: var(--momo-info-text); + } diff --git a/web/static/js/ewoooc-base.js b/web/static/js/ewoooc-base.js new file mode 100644 index 0000000..a56f102 --- /dev/null +++ b/web/static/js/ewoooc-base.js @@ -0,0 +1,98 @@ +/* EwoooC base interactions extracted from templates/ewoooc_base.html. */ + (function () { + if (window.__momoLinkGuardInstalled) return; + window.__momoLinkGuardInstalled = true; + + const MOMO_CODE_RE = /^[A-Za-z0-9_-]{4,}$/; + + function toText(value) { + return value == null ? '' : String(value); + } + + function isLikelyMomoCode(value) { + const cleaned = toText(value).trim(); + if (!cleaned) return false; + const lowered = cleaned.toLowerCase(); + if (['nan', 'none', 'null', 'undefined'].includes(lowered)) return false; + if (lowered.startsWith('momo_') || lowered.startsWith('manual_') || lowered.startsWith('pchome_')) return false; + return MOMO_CODE_RE.test(cleaned); + } + + function extractIcode(url) { + const target = toText(url).trim(); + if (!target) return ''; + try { + const parsed = new URL(target, location.origin); + return toText(parsed.searchParams.get('i_code')).trim(); + } catch (error) { + const match = /[?&]i_code=([^&#]+)/i.exec(target); + return match ? decodeURIComponent(match[1] || '').trim() : ''; + } + } + + function buildSafeMomoUrl(iCode) { + const cleaned = toText(iCode).trim(); + if (!isLikelyMomoCode(cleaned)) return ''; + return `https://www.momoshop.com.tw/goods/GoodsDetail.jsp?i_code=${encodeURIComponent(cleaned)}`; + } + + document.addEventListener('click', function (event) { + const link = event.target.closest ? event.target.closest('a.momo-tracked-link') : null; + if (!link) return; + + const href = toText(link.getAttribute('href')).trim(); + const original = toText(link.dataset && link.dataset.momoOriginalUrl).trim(); + const iCode = toText(link.dataset && (link.dataset.trackIcode || link.dataset.trackProductId)).trim() + || extractIcode(original) + || extractIcode(href); + const safeUrl = buildSafeMomoUrl(iCode); + + if (safeUrl && (!href || href === '#' || /ec404/i.test(href) || /ec404/i.test(original))) { + event.preventDefault(); + link.setAttribute('href', safeUrl); + window.open(safeUrl, link.getAttribute('target') || '_self', 'noopener,noreferrer'); + } + }, true); + })(); + +(function () { + const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content'); + window.fetchWithCSRF = (url, options = {}) => { + options.headers = { ...options.headers, 'X-CSRFToken': csrfToken }; + return fetch(url, options); + }; +})(); + + (function () { + const shell = document.getElementById('momo-shell'); + const toggle = document.querySelector('[data-momo-sidebar-toggle]'); + const close = document.querySelector('[data-momo-sidebar-close]'); + if (!shell || !toggle) return; + toggle.addEventListener('click', () => shell.classList.toggle('is-sidebar-open')); + if (close) close.addEventListener('click', () => shell.classList.remove('is-sidebar-open')); + })(); + + (function() { + const link = document.getElementById('momo-obs-link'); + const badge = document.getElementById('momo-obs-badge'); + if (!link || !badge) return; + async function refresh() { + try { + const r = await fetch('/observability/api/health_indicator', { credentials: 'same-origin' }); + if (!r.ok) return; + const d = await r.json(); + if (!d.ok) return; + link.title = d.tooltip || 'AI 觀測台'; + if (d.alert_count > 0) { + badge.textContent = d.alert_count; + badge.hidden = false; + link.classList.add('is-alert'); + } else { + badge.hidden = true; + link.classList.remove('is-alert'); + } + } catch (e) {} + } + refresh(); + setInterval(refresh, 60000); + })();