This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
================================================================================
|
||||
|
||||
【已完成】
|
||||
- V10.194 重整 `/observability/ppt_audit_history` 產線資訊階層:新增 Pipeline Health、五段式流程階段、排程/覆蓋/DB/預覽/視覺 QA 狀態摘要,讓「已產生」改為可判斷的目標產生、其他版本、待排程補齊等狀態。
|
||||
- V10.192 補 `/observability/ppt_audit_history` 最近可預覽簡報 workbench:最新 4 份 PPT 直接在控制台下方提供線上預覽與下載,降低使用者找檔案的操作成本;完整檔案清單仍保留在下方表格。
|
||||
- V10.190 補 `/observability/ppt_audit_file/<filename>` 站內線上預覽:PPTX 由 LibreOffice 轉 PDF 快取後以 iframe 預覽,保留原始 PPTX 下載;Dockerfile 加 `libreoffice-impress`,compose 預設啟用 `PPT_VISION_ENABLED=true`,PPT 產線頁新增視覺 QA 停用原因與更精簡的控制台式排版。
|
||||
- V10.188 補強 `/observability/ppt_audit_history` PPT 視覺 QA 產線:頁面明確呈現每日、每週、每月、每季、每半年、每年定期產出節奏,並顯示 `ppt_generation_runs` DB 寫入紀錄;保留自動補齊缺漏與資料庫/檔案覆蓋狀態。
|
||||
|
||||
@@ -2229,6 +2229,142 @@ def budget_update(budget_id: int):
|
||||
# /observability/ppt_audit_history — Phase 29 PPT 視覺審核歷史
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
def _build_ppt_pipeline_view(files, auto_generation, audit_stats, generation_runs, vision_status):
|
||||
"""Compose page-level PPT pipeline health so the template stays declarative."""
|
||||
files = files or []
|
||||
auto_generation = auto_generation or {}
|
||||
audit_stats = audit_stats or {}
|
||||
generation_runs = generation_runs or []
|
||||
vision_status = vision_status or {}
|
||||
|
||||
def _as_int(value):
|
||||
try:
|
||||
return int(value or 0)
|
||||
except Exception:
|
||||
return 0
|
||||
|
||||
def _as_float(value):
|
||||
try:
|
||||
return float(value or 0)
|
||||
except Exception:
|
||||
return 0.0
|
||||
|
||||
ready_count = _as_int(auto_generation.get('ready_count'))
|
||||
total_count = _as_int(auto_generation.get('total'))
|
||||
missing_count = _as_int(auto_generation.get('missing_count'))
|
||||
coverage_pct = round((ready_count / total_count * 100), 1) if total_count else 0
|
||||
valid_preview_count = sum(1 for item in files if item.get('file_exists') and item.get('is_valid_ppt'))
|
||||
broken_file_count = sum(1 for item in files if item.get('file_exists') and not item.get('is_valid_ppt'))
|
||||
db_backed_count = sum(1 for item in files if item.get('source') in ('database', 'both'))
|
||||
run_error_count = sum(1 for item in generation_runs if item.get('status') == 'error')
|
||||
run_ready_count = sum(1 for item in generation_runs if item.get('status') == 'ready')
|
||||
audit_total = _as_int(audit_stats.get('total'))
|
||||
audit_issues = _as_int(audit_stats.get('total_issues'))
|
||||
pass_rate = round(_as_float(audit_stats.get('pass_rate')), 1)
|
||||
latest_run = generation_runs[0] if generation_runs else {}
|
||||
latest_file = files[0] if files else {}
|
||||
|
||||
if not vision_status.get('ready'):
|
||||
health_status = 'partial'
|
||||
health_title = '視覺審核環境待確認'
|
||||
health_message = 'PPT 可產出與預覽,但 minicpm-v / LibreOffice 的 runtime 狀態仍需維持就緒。'
|
||||
elif run_error_count or broken_file_count:
|
||||
health_status = 'error'
|
||||
health_title = '產線有異常待處理'
|
||||
health_message = f'目前有 {run_error_count} 筆產出失敗、{broken_file_count} 份檔案不可預覽,應先處理最近錯誤。'
|
||||
elif missing_count > 0:
|
||||
health_status = 'partial'
|
||||
health_title = '定義簡報尚未全數補齊'
|
||||
health_message = f'本月已完成 {ready_count}/{total_count} 類,仍缺 {missing_count} 類,可等排程或手動補齊。'
|
||||
elif audit_total and pass_rate < 80:
|
||||
health_status = 'partial'
|
||||
health_title = '審核通過率偏低'
|
||||
health_message = f'本月視覺 QA 通過率 {pass_rate:.1f}%,需優先檢查失敗熱點與 RAG 修法建議。'
|
||||
elif total_count:
|
||||
health_status = 'ready'
|
||||
health_title = '產線覆蓋完整'
|
||||
health_message = '定義簡報、DB 紀錄、線上預覽與視覺 QA 都已具備可追蹤入口。'
|
||||
else:
|
||||
health_status = 'planned'
|
||||
health_title = '產線等待資料'
|
||||
health_message = '目前尚未讀到定義簡報覆蓋資料,頁面會保留安全空狀態。'
|
||||
|
||||
if audit_total:
|
||||
qa_value = f'{pass_rate:.0f}%'
|
||||
qa_meta = f'{audit_total} 筆審核 / {audit_issues} 個問題'
|
||||
qa_status = 'ready' if pass_rate >= 80 and audit_issues == 0 else 'partial'
|
||||
else:
|
||||
qa_value = '待審核'
|
||||
qa_meta = '每日報表才進入 minicpm-v 視覺 QA'
|
||||
qa_status = 'planned'
|
||||
|
||||
stages = [
|
||||
{
|
||||
'key': 'schedule',
|
||||
'icon': 'calendar-check',
|
||||
'label': '排程節奏',
|
||||
'value': '6 條',
|
||||
'meta': '每日 / 每週 / 每月 / 每季 / 半年 / 年度',
|
||||
'detail': auto_generation.get('cadence_summary') or '等待排程設定',
|
||||
'status': 'ready' if auto_generation.get('enabled') else 'partial',
|
||||
},
|
||||
{
|
||||
'key': 'coverage',
|
||||
'icon': 'diagram-project',
|
||||
'label': '定義覆蓋',
|
||||
'value': f'{ready_count}/{total_count}' if total_count else '—',
|
||||
'meta': f'{coverage_pct:.1f}% 完成',
|
||||
'detail': f'缺漏 {missing_count} 類' if missing_count else '當期目標完整',
|
||||
'status': 'ready' if total_count and missing_count == 0 else 'partial',
|
||||
},
|
||||
{
|
||||
'key': 'database',
|
||||
'icon': 'database',
|
||||
'label': 'DB 寫入',
|
||||
'value': f'{len(generation_runs)} 筆',
|
||||
'meta': f'{run_ready_count} 成功 / {run_error_count} 失敗',
|
||||
'detail': latest_run.get('started_at') or '尚無本月寫入紀錄',
|
||||
'status': 'error' if run_error_count else ('ready' if generation_runs else 'planned'),
|
||||
},
|
||||
{
|
||||
'key': 'preview',
|
||||
'icon': 'desktop',
|
||||
'label': '線上預覽',
|
||||
'value': f'{valid_preview_count} 份',
|
||||
'meta': f'{db_backed_count} 份含 DB 紀錄',
|
||||
'detail': latest_file.get('name') or '尚無可預覽檔案',
|
||||
'status': 'error' if broken_file_count else ('ready' if valid_preview_count else 'planned'),
|
||||
},
|
||||
{
|
||||
'key': 'qa',
|
||||
'icon': 'eye',
|
||||
'label': '視覺 QA',
|
||||
'value': qa_value,
|
||||
'meta': qa_meta,
|
||||
'detail': 'minicpm-v + RAG 修法 + AiderHeal',
|
||||
'status': qa_status,
|
||||
},
|
||||
]
|
||||
|
||||
return {
|
||||
'status': health_status,
|
||||
'title': health_title,
|
||||
'message': health_message,
|
||||
'ready_count': ready_count,
|
||||
'total_count': total_count,
|
||||
'missing_count': missing_count,
|
||||
'coverage_pct': coverage_pct,
|
||||
'valid_preview_count': valid_preview_count,
|
||||
'broken_file_count': broken_file_count,
|
||||
'db_backed_count': db_backed_count,
|
||||
'run_error_count': run_error_count,
|
||||
'pass_rate': pass_rate,
|
||||
'audit_total': audit_total,
|
||||
'latest_run': latest_run,
|
||||
'latest_file': latest_file,
|
||||
'stages': stages,
|
||||
}
|
||||
|
||||
@admin_observability_bp.route('/ppt_audit_history')
|
||||
@login_required
|
||||
def ppt_audit_history():
|
||||
@@ -2586,6 +2722,14 @@ def ppt_audit_history():
|
||||
except Exception:
|
||||
logger.debug("PPT auto-generation coverage unavailable", exc_info=True)
|
||||
|
||||
pipeline_view = _build_ppt_pipeline_view(
|
||||
files=files,
|
||||
auto_generation=auto_generation,
|
||||
audit_stats=audit_30d_stats,
|
||||
generation_runs=generation_runs,
|
||||
vision_status=vision_status,
|
||||
)
|
||||
|
||||
return render_template(
|
||||
'admin/ppt_audit_history.html',
|
||||
active_page='obs_ppt_audit',
|
||||
@@ -2607,6 +2751,7 @@ def ppt_audit_history():
|
||||
auto_generation_items=auto_generation.get('items', []),
|
||||
auto_generation_missing_report_types=auto_generation.get('missing_report_types', []),
|
||||
generation_runs=generation_runs,
|
||||
pipeline_view=pipeline_view,
|
||||
error=error,
|
||||
)
|
||||
|
||||
|
||||
@@ -289,10 +289,16 @@ def get_schedule_cadence_status(coverage_items: Sequence[dict] | None = None) ->
|
||||
total = len(report_types)
|
||||
if total and not missing_types:
|
||||
status = "ready"
|
||||
status_label = "當期完整"
|
||||
status_hint = "排程定義內的簡報都已找到目標版本。"
|
||||
elif ready_count > 0:
|
||||
status = "partial"
|
||||
status_label = f"已完成 {ready_count}/{total}"
|
||||
status_hint = "仍有部分簡報尚未補齊,需等排程或手動回補。"
|
||||
else:
|
||||
status = "missing"
|
||||
status_label = "待產出"
|
||||
status_hint = "當期尚未看到符合定義的簡報。"
|
||||
cadences.append({
|
||||
"key": key,
|
||||
"label": meta["label"],
|
||||
@@ -305,9 +311,13 @@ def get_schedule_cadence_status(coverage_items: Sequence[dict] | None = None) ->
|
||||
"ready_count": ready_count,
|
||||
"missing_count": len(missing_types),
|
||||
"missing_report_types": missing_types,
|
||||
"missing_report_labels": [REPORT_TYPE_LABELS.get(report_type, report_type) for report_type in missing_types],
|
||||
"total": total,
|
||||
"progress_pct": round((ready_count / total * 100), 1) if total else 0,
|
||||
"status": status,
|
||||
"status_label": status_label,
|
||||
"status_hint": status_hint,
|
||||
"coverage_text": f"{ready_count}/{total}",
|
||||
})
|
||||
return cadences
|
||||
|
||||
@@ -581,25 +591,42 @@ def get_defined_report_coverage(
|
||||
latest_generated_at[report_type] = datetime.fromtimestamp(mtime)
|
||||
latest_file_path[report_type] = str(path)
|
||||
|
||||
items = [
|
||||
{
|
||||
items = []
|
||||
for job in jobs:
|
||||
count = counts[job.report_type]
|
||||
exact_count = exact_counts[job.report_type]
|
||||
if exact_count > 0:
|
||||
status = "ready"
|
||||
status_label = "目標已產生"
|
||||
status_hint = "檔案參數與本期定義相符。"
|
||||
elif count > 0:
|
||||
status = "partial"
|
||||
status_label = "有其他版本"
|
||||
status_hint = "找到同類簡報,但參數或目標期別不完全相符。"
|
||||
else:
|
||||
status = "missing"
|
||||
status_label = "待排程補齊"
|
||||
status_hint = "尚未找到符合定義的檔案或 DB 紀錄。"
|
||||
items.append({
|
||||
"key": job.report_type,
|
||||
"label": job.label,
|
||||
"target_label": job.target_label,
|
||||
"count": counts[job.report_type],
|
||||
"exact_count": exact_counts[job.report_type],
|
||||
"ready": exact_counts[job.report_type] > 0,
|
||||
"has_other_versions": counts[job.report_type] > 0 and exact_counts[job.report_type] == 0,
|
||||
"count": count,
|
||||
"exact_count": exact_count,
|
||||
"ready": status == "ready",
|
||||
"has_other_versions": status == "partial",
|
||||
"status": status,
|
||||
"status_label": status_label,
|
||||
"status_hint": status_hint,
|
||||
"sources": sorted(sources[job.report_type]),
|
||||
"latest_generated_at": (
|
||||
latest_generated_at[job.report_type].strftime("%Y-%m-%d %H:%M")
|
||||
if latest_generated_at[job.report_type] else None
|
||||
),
|
||||
"latest_file_path": latest_file_path[job.report_type],
|
||||
"latest_file_name": os.path.basename(latest_file_path[job.report_type]) if latest_file_path[job.report_type] else "",
|
||||
"expected_params": job.expected_params,
|
||||
}
|
||||
for job in jobs
|
||||
]
|
||||
})
|
||||
missing = [item for item in items if not item["ready"]]
|
||||
cadences = get_schedule_cadence_status(items)
|
||||
return {
|
||||
|
||||
@@ -128,6 +128,31 @@
|
||||
</div>
|
||||
</section>
|
||||
{% endif %}
|
||||
<section class="ppt-health-board" aria-label="PPT 產線健康總覽">
|
||||
<div class="ppt-health-main is-{{ pipeline_view.status }}">
|
||||
<div class="ppt-label">Pipeline Health</div>
|
||||
<h2>{{ pipeline_view.title }}</h2>
|
||||
<p>{{ pipeline_view.message }}</p>
|
||||
<div class="ppt-health-facts">
|
||||
<span><strong>{{ pipeline_view.ready_count }}/{{ pipeline_view.total_count }}</strong> 定義覆蓋</span>
|
||||
<span><strong>{{ pipeline_view.valid_preview_count }}</strong> 份可預覽</span>
|
||||
<span><strong>{{ pipeline_view.audit_total }}</strong> 筆視覺 QA</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ppt-stage-grid">
|
||||
{% for stage in pipeline_view.stages %}
|
||||
<article class="ppt-stage-card is-{{ stage.status }}">
|
||||
<div class="ppt-stage-icon"><i class="fas fa-{{ stage.icon }}" aria-hidden="true"></i></div>
|
||||
<div>
|
||||
<div class="ppt-label">{{ stage.label }}</div>
|
||||
<strong>{{ stage.value }}</strong>
|
||||
<small>{{ stage.meta }}</small>
|
||||
<p>{{ stage.detail }}</p>
|
||||
</div>
|
||||
</article>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</section>
|
||||
<section class="ppt-panel mt-3"
|
||||
data-ppt-auto-generation
|
||||
data-auto-start="{{ 'true' if auto_generation.can_auto_start else 'false' }}"
|
||||
@@ -154,9 +179,15 @@
|
||||
<span style="width: {{ cadence.progress_pct }}%"></span>
|
||||
</div>
|
||||
<div class="ppt-cadence-meta">
|
||||
<span>{{ cadence.gate }}</span>
|
||||
{% if cadence.missing_count > 0 %}<span>缺 {{ cadence.missing_count }} 類</span>{% else %}<span>完整</span>{% endif %}
|
||||
<span>{{ cadence.status_label }}</span>
|
||||
<span>{{ cadence.coverage_text }}</span>
|
||||
</div>
|
||||
<p class="ppt-cadence-gate">{{ cadence.description }}</p>
|
||||
{% if cadence.missing_count > 0 %}
|
||||
<small class="text-muted">待補:{{ cadence.missing_report_labels[:3]|join('、') }}{% if cadence.missing_report_labels|length > 3 %} 等 {{ cadence.missing_report_labels|length }} 類{% endif %}</small>
|
||||
{% else %}
|
||||
<small class="status-good">{{ cadence.status_hint }}</small>
|
||||
{% endif %}
|
||||
</article>
|
||||
{% endfor %}
|
||||
</div>
|
||||
@@ -181,12 +212,13 @@
|
||||
<div class="ppt-coverage-list" aria-label="定義簡報覆蓋明細">
|
||||
{% for item in auto_generation_items %}
|
||||
<div class="ppt-coverage-row">
|
||||
<div>
|
||||
<strong>{{ item.label }}</strong>
|
||||
<small>{{ item.target_label or '最新資料' }}{% if item.latest_generated_at %} · {{ item.latest_generated_at }}{% endif %}</small>
|
||||
</div>
|
||||
<span class="ppt-run-status {% if item.ready %}is-ready{% elif item.has_other_versions %}is-partial{% else %}is-missing_file{% endif %}">
|
||||
{% if item.ready %}已產生{% elif item.has_other_versions %}其他版本{% else %}待補齊{% endif %}
|
||||
<div>
|
||||
<strong>{{ item.label }}</strong>
|
||||
<small>{{ item.target_label or '最新資料' }}{% if item.latest_generated_at %} · {{ item.latest_generated_at }}{% endif %}</small>
|
||||
<small>{{ item.status_hint }}</small>
|
||||
</div>
|
||||
<span class="ppt-run-status is-{{ item.status }}">
|
||||
{{ item.status_label }}
|
||||
</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
@@ -217,6 +249,11 @@
|
||||
<span class="ppt-run-target">{{ run.target_label or '最新資料' }}</span>
|
||||
<span class="ppt-run-status is-{{ run.status }}">{{ run.status_label }}</span>
|
||||
<small class="text-muted">{{ run.started_at }}{% if run.finished_at %} → {{ run.finished_at }}{% endif %}</small>
|
||||
{% if run.file_name %}
|
||||
<a class="btn btn-outline-primary btn-sm" href="{{ url_for('admin_observability.ppt_audit_file', filename=run.file_name) }}" target="_blank" rel="noopener">
|
||||
<i class="fas fa-eye me-1"></i>預覽
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
@@ -215,6 +215,10 @@ def test_ppt_audit_history_shows_ppt_schedule_and_db_runs(client, monkeypatch):
|
||||
assert text in html
|
||||
assert 'ppt_generation_runs' in html
|
||||
assert '每日日報' in html
|
||||
assert 'Pipeline Health' in html
|
||||
assert '排程節奏' in html
|
||||
assert 'DB 寫入' in html
|
||||
assert '線上預覽' in html
|
||||
|
||||
|
||||
def test_ppt_audit_history_shows_recent_preview_workbench(client, monkeypatch, tmp_path):
|
||||
|
||||
@@ -100,8 +100,12 @@ def test_coverage_marks_ready_from_database(monkeypatch):
|
||||
|
||||
by_key = {item["key"]: item for item in result["items"]}
|
||||
assert by_key["daily"]["ready"] is True
|
||||
assert by_key["daily"]["status"] == "ready"
|
||||
assert by_key["daily"]["status_label"] == "目標已產生"
|
||||
assert by_key["monthly"]["ready"] is True
|
||||
assert by_key["weekly"]["ready"] is False
|
||||
assert by_key["weekly"]["status"] == "missing"
|
||||
assert by_key["weekly"]["status_label"] == "待排程補齊"
|
||||
assert result["missing_count"] == 1
|
||||
|
||||
|
||||
@@ -145,6 +149,9 @@ def test_schedule_cadence_status_exposes_all_periodic_contracts():
|
||||
assert by_key["weekly"]["report_types"] == ["weekly", "market_intel"]
|
||||
assert by_key["weekly"]["ready_count"] == 1
|
||||
assert by_key["weekly"]["missing_report_types"] == ["weekly"]
|
||||
assert by_key["weekly"]["missing_report_labels"] == ["週報"]
|
||||
assert by_key["weekly"]["status_label"] == "已完成 1/2"
|
||||
assert by_key["weekly"]["coverage_text"] == "1/2"
|
||||
assert "TTM 滾動 12 月" in by_key["monthly"]["report_labels"]
|
||||
|
||||
|
||||
|
||||
@@ -182,6 +182,142 @@
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.ppt-health-board {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(280px, 0.36fr) minmax(0, 0.64fr);
|
||||
gap: var(--momo-space-4, 16px);
|
||||
margin-top: var(--momo-space-4, 16px);
|
||||
}
|
||||
|
||||
.ppt-health-main,
|
||||
.ppt-stage-card {
|
||||
border: 1px solid var(--obs-line);
|
||||
border-radius: var(--momo-radius-lg, 8px);
|
||||
background:
|
||||
radial-gradient(circle, rgba(45, 40, 32, 0.08) 1px, transparent 1.2px),
|
||||
rgba(255, 255, 255, 0.58);
|
||||
background-size: 10px 10px, auto;
|
||||
box-shadow: var(--momo-shadow-md, 0 16px 38px rgba(70, 46, 28, 0.08));
|
||||
}
|
||||
|
||||
.ppt-health-main {
|
||||
display: grid;
|
||||
align-content: space-between;
|
||||
min-height: 228px;
|
||||
padding: var(--momo-space-4, 16px);
|
||||
border-left: 5px solid var(--obs-blue);
|
||||
}
|
||||
|
||||
.ppt-health-main.is-ready {
|
||||
border-left-color: var(--obs-green);
|
||||
}
|
||||
|
||||
.ppt-health-main.is-partial,
|
||||
.ppt-health-main.is-planned {
|
||||
border-left-color: var(--obs-amber);
|
||||
}
|
||||
|
||||
.ppt-health-main.is-error {
|
||||
border-left-color: var(--obs-red);
|
||||
}
|
||||
|
||||
.ppt-health-main h2 {
|
||||
margin: var(--momo-space-2, 8px) 0;
|
||||
color: var(--obs-ink);
|
||||
font-size: var(--momo-text-title, 18px);
|
||||
font-weight: var(--momo-font-weight-black, 800);
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.ppt-health-main p {
|
||||
margin: 0;
|
||||
color: var(--obs-muted);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.ppt-health-facts {
|
||||
display: grid;
|
||||
gap: var(--momo-space-2, 8px);
|
||||
margin-top: var(--momo-space-4, 16px);
|
||||
}
|
||||
|
||||
.ppt-health-facts span {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: var(--momo-space-3, 12px);
|
||||
padding-top: var(--momo-space-2, 8px);
|
||||
border-top: 1px solid var(--obs-line);
|
||||
color: var(--obs-muted);
|
||||
font-size: var(--momo-text-caption, 12px);
|
||||
}
|
||||
|
||||
.ppt-health-facts strong {
|
||||
color: var(--obs-ink);
|
||||
font-size: var(--momo-text-body, 14px);
|
||||
}
|
||||
|
||||
.ppt-stage-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, minmax(0, 1fr));
|
||||
gap: var(--momo-space-3, 12px);
|
||||
}
|
||||
|
||||
.ppt-stage-card {
|
||||
display: grid;
|
||||
grid-template-columns: 34px minmax(0, 1fr);
|
||||
gap: var(--momo-space-2, 8px);
|
||||
min-height: 228px;
|
||||
padding: var(--momo-space-3, 12px);
|
||||
border-top: 4px solid var(--obs-blue);
|
||||
}
|
||||
|
||||
.ppt-stage-card.is-ready {
|
||||
border-top-color: var(--obs-green);
|
||||
}
|
||||
|
||||
.ppt-stage-card.is-partial,
|
||||
.ppt-stage-card.is-planned {
|
||||
border-top-color: var(--obs-amber);
|
||||
}
|
||||
|
||||
.ppt-stage-card.is-error {
|
||||
border-top-color: var(--obs-red);
|
||||
}
|
||||
|
||||
.ppt-stage-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border: 1px solid rgba(201, 100, 66, 0.24);
|
||||
border-radius: var(--momo-radius-md, 6px);
|
||||
color: var(--obs-accent);
|
||||
background: rgba(255, 248, 239, 0.78);
|
||||
}
|
||||
|
||||
.ppt-stage-card strong {
|
||||
display: block;
|
||||
margin: var(--momo-space-1, 4px) 0;
|
||||
color: var(--obs-ink);
|
||||
font-size: var(--momo-text-headline, 22px);
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.ppt-stage-card small,
|
||||
.ppt-stage-card p {
|
||||
display: block;
|
||||
margin: 0;
|
||||
color: var(--obs-muted);
|
||||
font-size: var(--momo-text-caption, 12px);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.ppt-stage-card p {
|
||||
margin-top: var(--momo-space-2, 8px);
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.ppt-grid {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.2fr) minmax(330px, 0.8fr);
|
||||
@@ -316,6 +452,12 @@
|
||||
font-size: var(--momo-text-caption, 12px);
|
||||
}
|
||||
|
||||
.ppt-cadence-tile > small {
|
||||
display: block;
|
||||
margin-top: var(--momo-space-2, 8px);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.ppt-coverage-score {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
@@ -415,6 +557,12 @@
|
||||
border-color: rgba(72, 108, 149, 0.35);
|
||||
}
|
||||
|
||||
.ppt-run-status.is-missing,
|
||||
.ppt-run-status.is-planned {
|
||||
color: var(--obs-amber);
|
||||
border-color: rgba(184, 121, 47, 0.35);
|
||||
}
|
||||
|
||||
.ppt-run-status.is-error,
|
||||
.ppt-run-status.is-missing_file {
|
||||
color: var(--obs-red);
|
||||
@@ -494,10 +642,15 @@
|
||||
}
|
||||
|
||||
.ppt-diagnostic-strip,
|
||||
.ppt-health-board,
|
||||
.ppt-pipeline-layout {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.ppt-stage-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.ppt-deck-rail {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
@@ -516,6 +669,7 @@
|
||||
.ppt-auto-grid,
|
||||
.ppt-mini-grid,
|
||||
.ppt-deck-rail,
|
||||
.ppt-stage-grid,
|
||||
.ppt-coverage-score,
|
||||
.ppt-coverage-list {
|
||||
grid-template-columns: 1fr;
|
||||
|
||||
Reference in New Issue
Block a user