SeaWolf-AI commited on
Commit
7e3f615
·
verified ·
1 Parent(s): 32c40dd

feat: render all-checks checklist (1/2 tabs) + strong consult-conversion CTA (ARTEX framing)

Browse files
Files changed (1) hide show
  1. app.py +46 -6
app.py CHANGED
@@ -134,6 +134,31 @@ def _metacards(meta, lang):
134
  return '<div style="display:flex;gap:6px;margin:8px 0">' + cells + '</div>'
135
 
136
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
137
  def _upsell(lang):
138
  return ('<div class="chdk" style="background:linear-gradient(135deg,#6d28d9,#8b5cf6);border-radius:14px;padding:16px 18px;margin:16px 0">'
139
  '<div style="font-weight:800;font-size:15px;color:#fff">🔓 ' + _L("전사이트 정밀 리포트 — ② 도메인 인증 탭", "Full-site report — ② Domain Verification tab", lang) + '</div>'
@@ -143,6 +168,22 @@ def _upsell(lang):
143
  + '</div></div>')
144
 
145
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
  def _cards(items, empty, lang):
147
  if not items:
148
  return '<div style="color:#067647;font-weight:700;padding:8px 0">%s</div>' % empty
@@ -312,6 +353,7 @@ def build_report(d, deep=False, verified_email=None, lang="ko", show_upsell=Fals
312
  + _h2("🧩 " + _L("구조화 데이터 커버리지", "Structured-data coverage", lang)) + '<div style="font-size:12.5px"><b style="color:#067647">' + _L("보유:", "Present:", lang) + '</b> ' + sch + '<br><b style="color:#b42318;display:inline-block;margin-top:6px">' + _L("누락:", "Missing:", lang) + '</b> ' + schm + '</div>'
313
  + _h2("📧 " + _L("이메일 위조 방지", "Email anti-spoofing", lang)) + '<div style="display:flex;gap:10px">' + _pill(es.get("spf"), "SPF") + " " + _pill(es.get("dmarc"), "DMARC") + '</div>'
314
  + incon
 
315
  + _h2("🗂 " + (_L("점검 페이지 (%d)", "Scanned pages (%d)", lang) % dp.get("pages", 0))) + '<div style="max-height:220px;overflow:auto;border:1px solid #eef1f6;border-radius:8px;padding:8px 12px">' + pglist + '</div>'
316
  )
317
 
@@ -338,19 +380,17 @@ def build_report(d, deep=False, verified_email=None, lang="ko", show_upsell=Fals
338
  + '<div style="flex:1;border:1px solid #e4e9ef;border-radius:10px;padding:10px"><b style="color:#b54708;font-size:22px">%d</b><div style="font-size:11px;color:#6b7280">%s</div></div>' % (nmid, _L("보완", "Advisory", lang))
339
  + '<div style="flex:1;border:1px solid #e4e9ef;border-radius:10px;padding:10px"><b style="color:#8a6d00;font-size:22px">%d</b><div style="font-size:11px;color:#6b7280">%s</div></div>' % (nlow, _L("낮음", "Low", lang))
340
  + '</div>'
341
- + _h2("🛡 " + _L("보안 점검", "Security checks", lang)) + _locks(d.get('locks_on', 0), lang) + _cards(sec, _L("✓ 외부에서 보이는 보안 위생 문제 없음", "✓ No externally visible security-hygiene issues", lang), lang)
342
- + _h2("📈 " + _L("SEO 토대 (검색 노출)", "SEO foundation (search visibility)", lang)) + _metacards(meta, lang) + _cards(seo, _L("✓ SEO 기본 요소 양호", "✓ SEO fundamentals are healthy", lang), lang)
343
  + _h2("🤖 " + _L("AI 로봇 접근 (AI 가시성)", "AI crawler access (AI visibility)", lang))
344
  + '<div style="font-size:12.5px;color:#4b5563;margin:4px 0 8px">' + _L("생성형 AI가 당신을 인용하려면 로봇이 먼저 들어와야 합니다.", "For generative AI to cite you, its crawlers must get in first.", lang) + '</div>'
345
- + _traffic(d.get('matrix', {}), lang) + _cards(geo, _L("✓ 기계 판독 신호 양호", "✓ Machine-readability signals are healthy", lang), lang)
346
  + '<div style="font-size:12px;color:#6b7280;margin-top:6px">' + _L("제목", "Title", lang) + ': ' + title80 + ' · H1 ' + str(meta.get('h1', 0)) + ' · ' + _L("구조화", "Schema", lang) + ' ' + str(meta.get('jsonld', 0)) + ' · ' + _L("홈", "Home", lang) + ' ' + str(meta.get('size_kb', 0)) + 'KB · alt ' + str(meta.get('alt_cov', 0)) + '%</div>'
347
  + deep_html
348
  + (_upsell(lang) if show_upsell else "")
349
  + _h2("✓ " + _L("잘 되어 있는 점", "What's done well", lang)) + '<div>' + good + '</div>'
350
  + _h2("📌 " + _L("조치 우선순위", "Action priorities", lang)) + _priority(allf, lang)
351
- + '<div style="background:#f4f0ff;border:1px dashed #b89cff;border-radius:12px;padding:14px 16px;margin-top:16px"><b style="color:#6d28d9">' + _L("정밀 진단 안내", "About in-depth diagnosis", lang) + '</b><br><span style="font-size:13px;color:#374151">' + _L(
352
- "본 진단은 <b>비침투(읽기전용)</b>입니다. SQL삽입·파일업로드·인증우회 등 실제 취약점과 금융보안원 17종 에이전트 보안 정밀 점검은 <b>상담(3단계)</b>으로 제공합니다.",
353
- "This diagnosis is <b>non-intrusive (read-only)</b>. Real vulnerabilities (SQLi, file upload, auth bypass) and the FSI 17-item AI-agent security review are provided via <b>Consultation (step 3)</b>.", lang) + '</span></div>'
354
  + '<div style="color:#9ca3af;font-size:11px;margin-top:14px;border-top:1px solid #e4e9ef;padding-top:10px">' + _L(
355
  "본 진단은 비침투·비파괴로 수행되었으며 대상 서비스에 영향을 주지 않았습니다. 결과는 측정 시점(" + today + ") 기준. 서비스: 퀀트ID(QuantID) · 엔진: 치토스(CHITOS).",
356
  "This diagnosis was non-intrusive and non-destructive and did not affect the target. Results as of " + today + ". Service: QuantID · Engine: CHITOS.", lang) + '</div>'
 
134
  return '<div style="display:flex;gap:6px;margin:8px 0">' + cells + '</div>'
135
 
136
 
137
+ def _consult_cta(nhi, nmid, nlow, lang):
138
+ total = nhi + nmid + nlow
139
+ if lang == "ko":
140
+ risk = '<b style="color:#b42318">%d</b>개 개선점(높음 %d · 보완 %d · 낮음 %d)' % (total, nhi, nmid, nlow)
141
+ body = ("이 진단에서 " + risk + "이 확인됐습니다. 하지만 <b>비침투 스캔은 ‘밖에서 보이는 표면’까지</b>입니다 — "
142
+ "SQL삽입·파일업로드·인증우회·IDOR 같은 <b>실제 취약점</b>, 그리고 <b>ARTEX</b>처럼 AI 에이전트가 자동으로 뚫는 위협은 "
143
+ "<b>능동 정밀 진단</b>으로만 확인됩니다.")
144
+ areas = "웹서비스 · AX 인프라 · AI 모델 · AI 에이전트(AX-RAY) 4대 진단 + 금융보안원 17항목 레드티밍"
145
+ go = "👉 <b>③ 정밀 진단 상담</b> 탭에서 분야를 선택해 신청하세요. (서면 허가 기반·국내외 준거 수용)"
146
+ head = "🎯 다음 단계 — 정밀 진단 상담"
147
+ else:
148
+ risk = '<b style="color:#b42318">%d</b> improvement points (High %d · Advisory %d · Low %d)' % (total, nhi, nmid, nlow)
149
+ body = ("This scan found " + risk + ". But a <b>non-intrusive scan only shows the surface</b> — "
150
+ "<b>real vulnerabilities</b> like SQLi, file upload, auth bypass and IDOR, and threats where an AI agent breaks in automatically (as with <b>ARTEX</b>), "
151
+ "are confirmed only by <b>active in-depth testing</b>.")
152
+ areas = "Web Service · AX Infra · AI Model · AI Agent (AX-RAY) — 4 diagnoses + FSI 17-item red-teaming"
153
+ go = "👉 Request it in the <b>③ In-Depth Consultation</b> tab (authorization-based · aligned to domestic & international standards)."
154
+ head = "🎯 Next step — in-depth consultation"
155
+ return ('<div class="chdk" style="background:linear-gradient(135deg,#4c1d95,#7c3aed);border-radius:16px;padding:18px 20px;margin-top:18px;color:#f3e8ff">'
156
+ '<div style="font-size:16px;font-weight:800;color:#fff">' + head + '</div>'
157
+ '<div style="font-size:13px;margin-top:8px;line-height:1.55">' + body + '</div>'
158
+ '<div style="background:#ffffff1f;border-radius:10px;padding:9px 13px;margin-top:10px;font-size:12.5px;color:#fff">🛡 ' + areas + '</div>'
159
+ '<div style="font-size:13px;margin-top:10px;font-weight:700;color:#fff">' + go + '</div></div>')
160
+
161
+
162
  def _upsell(lang):
163
  return ('<div class="chdk" style="background:linear-gradient(135deg,#6d28d9,#8b5cf6);border-radius:14px;padding:16px 18px;margin:16px 0">'
164
  '<div style="font-weight:800;font-size:15px;color:#fff">🔓 ' + _L("전사이트 정밀 리포트 — ② 도메인 인증 탭", "Full-site report — ② Domain Verification tab", lang) + '</div>'
 
168
  + '</div></div>')
169
 
170
 
171
+ def _checklist(items, lang):
172
+ if not items:
173
+ return ""
174
+ ic = {"pass": ("✓", "#16a34a", "#f0fdf4"), "warn": ("!", "#b54708", "#fffaeb"), "fail": ("✕", "#b42318", "#fef2f2")}
175
+ npass = len([i for i in items if i[0] == "pass"])
176
+ cells = ""
177
+ for st, lab in items:
178
+ sym, col, bg = ic.get(st, ic["warn"])
179
+ cells += ('<div style="display:flex;gap:8px;align-items:center;padding:5px 9px;border:1px solid #eef1f6;border-radius:7px;background:%s">'
180
+ '<span style="color:%s;font-weight:900;width:13px;text-align:center;flex:none">%s</span>'
181
+ '<span style="font-size:12px;color:#374151">%s</span></div>') % (bg, col, sym, html.escape(_P(lab, lang)))
182
+ head = (_L("점검 항목 전체", "All checks", lang) + ' · <b style="color:#16a34a">%d/%d %s</b>' % (npass, len(items), _L("통과", "pass", lang)))
183
+ return ('<div style="font-size:11.5px;color:#6b7280;margin:8px 0 5px">📋 ' + head + '</div>'
184
+ '<div style="display:grid;grid-template-columns:1fr 1fr;gap:5px;margin-bottom:6px">' + cells + '</div>')
185
+
186
+
187
  def _cards(items, empty, lang):
188
  if not items:
189
  return '<div style="color:#067647;font-weight:700;padding:8px 0">%s</div>' % empty
 
353
  + _h2("🧩 " + _L("구조화 데이터 커버리지", "Structured-data coverage", lang)) + '<div style="font-size:12.5px"><b style="color:#067647">' + _L("보유:", "Present:", lang) + '</b> ' + sch + '<br><b style="color:#b42318;display:inline-block;margin-top:6px">' + _L("누락:", "Missing:", lang) + '</b> ' + schm + '</div>'
354
  + _h2("📧 " + _L("이메일 위조 방지", "Email anti-spoofing", lang)) + '<div style="display:flex;gap:10px">' + _pill(es.get("spf"), "SPF") + " " + _pill(es.get("dmarc"), "DMARC") + '</div>'
355
  + incon
356
+ + _h2("📋 " + _L("전사이트 점검 체크리스트", "Full-site checklist", lang)) + _checklist(dp.get("checks", []), lang)
357
  + _h2("🗂 " + (_L("점검 페이지 (%d)", "Scanned pages (%d)", lang) % dp.get("pages", 0))) + '<div style="max-height:220px;overflow:auto;border:1px solid #eef1f6;border-radius:8px;padding:8px 12px">' + pglist + '</div>'
358
  )
359
 
 
380
  + '<div style="flex:1;border:1px solid #e4e9ef;border-radius:10px;padding:10px"><b style="color:#b54708;font-size:22px">%d</b><div style="font-size:11px;color:#6b7280">%s</div></div>' % (nmid, _L("보완", "Advisory", lang))
381
  + '<div style="flex:1;border:1px solid #e4e9ef;border-radius:10px;padding:10px"><b style="color:#8a6d00;font-size:22px">%d</b><div style="font-size:11px;color:#6b7280">%s</div></div>' % (nlow, _L("낮음", "Low", lang))
382
  + '</div>'
383
+ + _h2("🛡 " + _L("보안 점검", "Security checks", lang)) + _locks(d.get('locks_on', 0), lang) + _cards(sec, _L("✓ 외부에서 보이는 보안 위생 문제 없음", "✓ No externally visible security-hygiene issues", lang), lang) + _checklist(d.get('checks', {}).get('sec', []), lang)
384
+ + _h2("📈 " + _L("SEO 토대 (검색 노출)", "SEO foundation (search visibility)", lang)) + _metacards(meta, lang) + _cards(seo, _L("✓ SEO 기본 요소 양호", "✓ SEO fundamentals are healthy", lang), lang) + _checklist(d.get('checks', {}).get('seo', []), lang)
385
  + _h2("🤖 " + _L("AI 로봇 접근 (AI 가시성)", "AI crawler access (AI visibility)", lang))
386
  + '<div style="font-size:12.5px;color:#4b5563;margin:4px 0 8px">' + _L("생성형 AI가 당신을 인용하려면 로봇이 먼저 들어와야 합니다.", "For generative AI to cite you, its crawlers must get in first.", lang) + '</div>'
387
+ + _traffic(d.get('matrix', {}), lang) + _cards(geo, _L("✓ 기계 판독 신호 양호", "✓ Machine-readability signals are healthy", lang), lang) + _checklist(d.get('checks', {}).get('geo', []), lang)
388
  + '<div style="font-size:12px;color:#6b7280;margin-top:6px">' + _L("제목", "Title", lang) + ': ' + title80 + ' · H1 ' + str(meta.get('h1', 0)) + ' · ' + _L("구조화", "Schema", lang) + ' ' + str(meta.get('jsonld', 0)) + ' · ' + _L("홈", "Home", lang) + ' ' + str(meta.get('size_kb', 0)) + 'KB · alt ' + str(meta.get('alt_cov', 0)) + '%</div>'
389
  + deep_html
390
  + (_upsell(lang) if show_upsell else "")
391
  + _h2("✓ " + _L("잘 되어 있는 점", "What's done well", lang)) + '<div>' + good + '</div>'
392
  + _h2("📌 " + _L("조치 우선순위", "Action priorities", lang)) + _priority(allf, lang)
393
+ + _consult_cta(nhi, nmid, nlow, lang)
 
 
394
  + '<div style="color:#9ca3af;font-size:11px;margin-top:14px;border-top:1px solid #e4e9ef;padding-top:10px">' + _L(
395
  "본 진단은 비침투·비파괴로 수행되었으며 대상 서비스에 영향을 주지 않았습니다. 결과는 측정 시점(" + today + ") 기준. 서비스: 퀀트ID(QuantID) · 엔진: 치토스(CHITOS).",
396
  "This diagnosis was non-intrusive and non-destructive and did not affect the target. Results as of " + today + ". Service: QuantID · Engine: CHITOS.", lang) + '</div>'