diff --git a/config.py b/config.py index b8cd805..31a001d 100644 --- a/config.py +++ b/config.py @@ -320,7 +320,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '') # ========================================== # 系統版本與路徑 # ========================================== -SYSTEM_VERSION = "V10.134" +SYSTEM_VERSION = "V10.135" LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log') public_url = PUBLIC_URL # 用於模板顯示 diff --git a/static/css/observability-system.css b/static/css/observability-system.css index fac4482..58addfe 100644 --- a/static/css/observability-system.css +++ b/static/css/observability-system.css @@ -2545,6 +2545,95 @@ background-size: var(--obs-matrix-size) !important; } +/* v3.12 responsive data surfaces: observability tables should read like + * operational records on phones, not desktop sheets squeezed into a viewport. + */ +.momo-observability-mode .table-responsive { + overflow-x: visible !important; +} + +.momo-observability-mode .table-responsive .table { + min-width: 0 !important; + width: 100% !important; + table-layout: auto; +} + +.momo-observability-mode .table :is(th, td) { + max-width: min(24rem, 38vw); + white-space: normal !important; +} + +@media (max-width: 768px) { + .momo-observability-mode .table-responsive { + max-height: none !important; + overflow: visible !important; + } + + .momo-observability-mode .table-responsive .obs-card-ready, + .momo-observability-mode .table-responsive .obs-card-ready thead, + .momo-observability-mode .table-responsive .obs-card-ready tbody, + .momo-observability-mode .table-responsive .obs-card-ready tr, + .momo-observability-mode .table-responsive .obs-card-ready td { + display: block; + width: 100% !important; + } + + .momo-observability-mode .table-responsive .obs-card-ready thead { + position: absolute; + width: 1px !important; + height: 1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + } + + .momo-observability-mode .table-responsive .obs-card-ready tbody { + display: grid; + gap: 0.7rem; + } + + .momo-observability-mode .table-responsive .obs-card-ready tr { + display: grid; + gap: 0.44rem; + padding: 0.72rem; + border: 1px solid var(--obs-line); + border-radius: var(--momo-radius-lg, 8px); + background: + var(--obs-matrix-dot-soft), + var(--momo-bg-elevated, #fdfaf3); + background-size: var(--obs-matrix-size), auto; + box-shadow: var(--momo-shadow-sm, 0 1px 3px rgba(42, 37, 32, 0.05)); + } + + .momo-observability-mode .table-responsive .obs-card-ready td { + display: grid; + grid-template-columns: minmax(5.4rem, 35%) minmax(0, 1fr); + gap: 0.58rem; + align-items: start; + max-width: 100%; + padding: 0 !important; + border: 0 !important; + border-radius: 0 !important; + background: transparent !important; + box-shadow: none !important; + text-align: left !important; + } + + .momo-observability-mode .table-responsive .obs-card-ready td::before { + content: attr(data-label); + color: var(--obs-muted); + font-family: var(--momo-font-mono, "JetBrains Mono", ui-monospace, monospace); + font-size: 0.72rem; + font-weight: 700; + line-height: 1.45; + overflow-wrap: anywhere; + } + + .momo-observability-mode .table-responsive .obs-card-ready td:empty { + display: none; + } +} + .momo-observability-mode :is( .obs-panel, .agent-panel, diff --git a/templates/ewoooc_base.html b/templates/ewoooc_base.html index e435d0b..3b6498d 100644 --- a/templates/ewoooc_base.html +++ b/templates/ewoooc_base.html @@ -528,6 +528,14 @@ })(); + {% if active_page|default('') in [ + 'obs_overview', 'obs_agent_orchestration', 'obs_business_intel', + 'obs_host_health', 'obs_ai_calls', 'obs_budget', + 'obs_promotion_review', 'obs_rag_queries', 'obs_quality_trend', + 'obs_ppt_audit' + ] %} + + {% endif %} {% block extra_js %}{% endblock %} {% block extra_scripts %}{% endblock %} diff --git a/web/static/css/observability-system.css b/web/static/css/observability-system.css index fac4482..58addfe 100644 --- a/web/static/css/observability-system.css +++ b/web/static/css/observability-system.css @@ -2545,6 +2545,95 @@ background-size: var(--obs-matrix-size) !important; } +/* v3.12 responsive data surfaces: observability tables should read like + * operational records on phones, not desktop sheets squeezed into a viewport. + */ +.momo-observability-mode .table-responsive { + overflow-x: visible !important; +} + +.momo-observability-mode .table-responsive .table { + min-width: 0 !important; + width: 100% !important; + table-layout: auto; +} + +.momo-observability-mode .table :is(th, td) { + max-width: min(24rem, 38vw); + white-space: normal !important; +} + +@media (max-width: 768px) { + .momo-observability-mode .table-responsive { + max-height: none !important; + overflow: visible !important; + } + + .momo-observability-mode .table-responsive .obs-card-ready, + .momo-observability-mode .table-responsive .obs-card-ready thead, + .momo-observability-mode .table-responsive .obs-card-ready tbody, + .momo-observability-mode .table-responsive .obs-card-ready tr, + .momo-observability-mode .table-responsive .obs-card-ready td { + display: block; + width: 100% !important; + } + + .momo-observability-mode .table-responsive .obs-card-ready thead { + position: absolute; + width: 1px !important; + height: 1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + } + + .momo-observability-mode .table-responsive .obs-card-ready tbody { + display: grid; + gap: 0.7rem; + } + + .momo-observability-mode .table-responsive .obs-card-ready tr { + display: grid; + gap: 0.44rem; + padding: 0.72rem; + border: 1px solid var(--obs-line); + border-radius: var(--momo-radius-lg, 8px); + background: + var(--obs-matrix-dot-soft), + var(--momo-bg-elevated, #fdfaf3); + background-size: var(--obs-matrix-size), auto; + box-shadow: var(--momo-shadow-sm, 0 1px 3px rgba(42, 37, 32, 0.05)); + } + + .momo-observability-mode .table-responsive .obs-card-ready td { + display: grid; + grid-template-columns: minmax(5.4rem, 35%) minmax(0, 1fr); + gap: 0.58rem; + align-items: start; + max-width: 100%; + padding: 0 !important; + border: 0 !important; + border-radius: 0 !important; + background: transparent !important; + box-shadow: none !important; + text-align: left !important; + } + + .momo-observability-mode .table-responsive .obs-card-ready td::before { + content: attr(data-label); + color: var(--obs-muted); + font-family: var(--momo-font-mono, "JetBrains Mono", ui-monospace, monospace); + font-size: 0.72rem; + font-weight: 700; + line-height: 1.45; + overflow-wrap: anywhere; + } + + .momo-observability-mode .table-responsive .obs-card-ready td:empty { + display: none; + } +} + .momo-observability-mode :is( .obs-panel, .agent-panel, diff --git a/web/static/js/observability-system.js b/web/static/js/observability-system.js new file mode 100644 index 0000000..f53c27d --- /dev/null +++ b/web/static/js/observability-system.js @@ -0,0 +1,50 @@ +(function () { + 'use strict'; + + function isObservabilityPage() { + return document.querySelector('.momo-observability-mode') !== null; + } + + function normalizeLabel(text) { + return (text || '').replace(/\s+/g, ' ').trim(); + } + + function enhanceTable(table) { + if (!table || table.dataset.obsCardReady === '1') return; + const headers = Array.from(table.querySelectorAll('thead th')).map((th) => normalizeLabel(th.textContent)); + if (!headers.length) return; + + table.querySelectorAll('tbody tr').forEach((row) => { + Array.from(row.children).forEach((cell, index) => { + if (!cell.hasAttribute('data-label')) { + cell.setAttribute('data-label', headers[index] || ''); + } + }); + }); + + table.dataset.obsCardReady = '1'; + table.classList.add('obs-card-ready'); + } + + function enhanceTables() { + if (!isObservabilityPage()) return; + document.querySelectorAll('.table-responsive table').forEach(enhanceTable); + } + + document.addEventListener('DOMContentLoaded', enhanceTables); + + let queued = false; + const observer = new MutationObserver(() => { + if (queued) return; + queued = true; + window.requestAnimationFrame(() => { + queued = false; + enhanceTables(); + }); + }); + + document.addEventListener('DOMContentLoaded', () => { + if (!isObservabilityPage()) return; + observer.observe(document.body, { childList: true, subtree: true }); + }); +})();