--
This commit is contained in:
@@ -30,17 +30,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
// ---- JSON-copy button (inside the JSON tab) ----
|
||||
const copyJsonBtn = document.getElementById("tls-copy-json");
|
||||
const rawJsonEl = document.getElementById("tls-raw-json");
|
||||
if (copyJsonBtn && rawJsonEl) {
|
||||
const origLabel = copyJsonBtn.textContent;
|
||||
copyJsonBtn.addEventListener("click", async () => {
|
||||
const text = rawJsonEl.textContent || "";
|
||||
// ---- Shared copy helper (clipboard API + textarea fallback) ----
|
||||
async function copyText(text) {
|
||||
try {
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
await navigator.clipboard.writeText(text);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
const ta = document.createElement("textarea");
|
||||
ta.value = text;
|
||||
ta.style.position = "fixed";
|
||||
@@ -49,20 +45,35 @@
|
||||
ta.select();
|
||||
try { document.execCommand("copy"); } catch (_) {}
|
||||
document.body.removeChild(ta);
|
||||
}
|
||||
copyJsonBtn.textContent = "コピーしました";
|
||||
copyJsonBtn.classList.add("is-done");
|
||||
setTimeout(() => {
|
||||
copyJsonBtn.textContent = origLabel;
|
||||
copyJsonBtn.classList.remove("is-done");
|
||||
}, 1500);
|
||||
return true;
|
||||
} catch (_) {
|
||||
copyJsonBtn.textContent = "コピー失敗";
|
||||
setTimeout(() => { copyJsonBtn.textContent = origLabel; }, 1500);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Swap a button's label and is-done class for 1.5s based on action result.
|
||||
function bindCopyButton(btn, getText) {
|
||||
if (!btn) return;
|
||||
const orig = btn.textContent;
|
||||
btn.addEventListener("click", async () => {
|
||||
const ok = await copyText(getText());
|
||||
btn.textContent = ok ? "コピーしました" : "コピー失敗";
|
||||
if (ok) btn.classList.add("is-done");
|
||||
setTimeout(() => {
|
||||
btn.textContent = orig;
|
||||
btn.classList.remove("is-done");
|
||||
}, 1500);
|
||||
});
|
||||
}
|
||||
|
||||
// ---- JSON-copy button (inside the JSON tab) ----
|
||||
const rawJsonEl = document.getElementById("tls-raw-json");
|
||||
bindCopyButton(document.getElementById("tls-copy-json"), () => (rawJsonEl && rawJsonEl.textContent) || "");
|
||||
|
||||
// ---- Copy-link button ----
|
||||
const copyLinkBtn = document.getElementById("tls-copy-link");
|
||||
bindCopyButton(copyLinkBtn, () => (copyLinkBtn && copyLinkBtn.dataset.link) || location.href);
|
||||
|
||||
// ---- PDF / print button ----
|
||||
const printBtn = document.getElementById("tls-print-pdf");
|
||||
if (printBtn) {
|
||||
@@ -88,37 +99,4 @@
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ---- Copy-link button ----
|
||||
const copyBtn = document.getElementById("tls-copy-link");
|
||||
if (copyBtn) {
|
||||
const origLabel = copyBtn.textContent;
|
||||
copyBtn.addEventListener("click", async () => {
|
||||
const link = copyBtn.dataset.link || location.href;
|
||||
try {
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
await navigator.clipboard.writeText(link);
|
||||
} else {
|
||||
// Fallback: use a hidden textarea + document.execCommand
|
||||
const ta = document.createElement("textarea");
|
||||
ta.value = link;
|
||||
ta.style.position = "fixed";
|
||||
ta.style.opacity = "0";
|
||||
document.body.appendChild(ta);
|
||||
ta.select();
|
||||
try { document.execCommand("copy"); } catch (_) {}
|
||||
document.body.removeChild(ta);
|
||||
}
|
||||
copyBtn.textContent = "コピーしました";
|
||||
copyBtn.classList.add("is-done");
|
||||
setTimeout(() => {
|
||||
copyBtn.textContent = origLabel;
|
||||
copyBtn.classList.remove("is-done");
|
||||
}, 1500);
|
||||
} catch (_) {
|
||||
copyBtn.textContent = "コピー失敗";
|
||||
setTimeout(() => { copyBtn.textContent = origLabel; }, 1500);
|
||||
}
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -1,11 +1,4 @@
|
||||
/* =====================================================
|
||||
* Nercone TLS Test — shared styles
|
||||
* Palette comes from /public/assets/css/style.css
|
||||
* #1A1A1A = page bg #272727 = block bg
|
||||
* #202020 = input bg #3a3a3a = border
|
||||
* #E0E0E0 = tx #939393 = light-grey-alt
|
||||
* #00C878 = bright-green #00C0FF = bright-blue
|
||||
* ===================================================== */
|
||||
/* Nercone TLS Test — shared styles. Palette uses CSS vars from /assets/css/style.css. */
|
||||
|
||||
/* ------ Landing page ------ */
|
||||
.tls-landing {
|
||||
@@ -22,7 +15,6 @@
|
||||
margin: 0 0 8px 0;
|
||||
font-size: clamp(40pt, 6vw, 64pt);
|
||||
line-height: 1.1;
|
||||
color: #FFFFFF;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
.tls-landing-subtitle {
|
||||
@@ -40,21 +32,21 @@
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
background-color: #3a3a3a;
|
||||
color: #E0E0E0;
|
||||
color: var(--color-light-grey);
|
||||
border: 1px solid #4a4a4a;
|
||||
border-radius: 6px;
|
||||
padding: 12px 16px;
|
||||
font-family: inherit;
|
||||
font-size: 13pt;
|
||||
}
|
||||
.tls-landing-input::placeholder { color: #939393; }
|
||||
.tls-landing-input::placeholder { color: var(--color-light-grey-alt); }
|
||||
.tls-landing-input:focus {
|
||||
outline: none;
|
||||
border-color: #00C0FF;
|
||||
border-color: var(--color-bright-blue);
|
||||
}
|
||||
.tls-landing-submit {
|
||||
background-color: #0096D0;
|
||||
color: #FFFFFF;
|
||||
color: var(--color-white);
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
padding: 12px 28px;
|
||||
@@ -75,10 +67,10 @@
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.tls-landing-links a {
|
||||
color: #939393;
|
||||
color: var(--color-light-grey-alt);
|
||||
text-decoration: underline;
|
||||
}
|
||||
.tls-landing-links a:hover { color: #E0E0E0; }
|
||||
.tls-landing-links a:hover { color: var(--color-light-grey); }
|
||||
|
||||
.tls-aux-section {
|
||||
max-width: 900px;
|
||||
@@ -110,9 +102,6 @@
|
||||
.tls-status-testid {
|
||||
margin: 10px 0 0 0;
|
||||
}
|
||||
.tls-status-testid code {
|
||||
font-family: "MesloLGS Nerd Font", "MesloLGS NF", "Menlo", "Consolas", monospace;
|
||||
}
|
||||
.tls-progress-track {
|
||||
width: 100%;
|
||||
height: 6px;
|
||||
@@ -122,7 +111,7 @@
|
||||
}
|
||||
.tls-progress-bar {
|
||||
height: 100%;
|
||||
background-color: #00C878;
|
||||
background-color: var(--color-bright-green);
|
||||
transition: width 0.25s ease;
|
||||
}
|
||||
|
||||
@@ -130,7 +119,7 @@
|
||||
.tls-log-wrap {
|
||||
width: min(1100px, 100%);
|
||||
margin: 0 auto;
|
||||
background-color: #272727;
|
||||
background-color: var(--color-dark-grey-alt);
|
||||
border-radius: 8px;
|
||||
padding: 18px 22px;
|
||||
text-align: left;
|
||||
@@ -183,10 +172,10 @@
|
||||
.tls-results-header {
|
||||
position: static;
|
||||
display: block;
|
||||
background-color: #272727;
|
||||
background-color: var(--color-dark-grey-alt);
|
||||
padding: 28px clamp(16px, 4vw, 40px) 0;
|
||||
margin-bottom: 0;
|
||||
border-bottom: 1px solid #1a1a1a;
|
||||
border-bottom: 1px solid var(--color-dark-grey);
|
||||
backdrop-filter: none;
|
||||
}
|
||||
.tls-results-header::before {
|
||||
@@ -234,7 +223,6 @@
|
||||
margin: 0 0 4px 0;
|
||||
font-size: 22pt;
|
||||
font-weight: 700;
|
||||
color: #FFFFFF;
|
||||
word-break: break-all;
|
||||
}
|
||||
.tls-results-metaline {
|
||||
@@ -249,7 +237,6 @@
|
||||
font-size: 10pt;
|
||||
}
|
||||
.tls-results-testid code {
|
||||
font-family: "MesloLGS Nerd Font", "MesloLGS NF", "Menlo", "Consolas", monospace;
|
||||
padding: 0;
|
||||
}
|
||||
.tls-results-actions {
|
||||
@@ -271,15 +258,15 @@
|
||||
}
|
||||
.tls-btn-secondary {
|
||||
background-color: #3a3a3a;
|
||||
color: #E0E0E0;
|
||||
color: var(--color-light-grey);
|
||||
}
|
||||
.tls-btn-secondary:hover { background-color: #4a4a4a; }
|
||||
.tls-btn-primary {
|
||||
background-color: #0096D0;
|
||||
color: #FFFFFF;
|
||||
color: var(--color-white);
|
||||
}
|
||||
.tls-btn-primary:hover { background-color: #00B4F0; }
|
||||
.tls-btn-primary.is-done { background-color: #00A050; }
|
||||
.tls-btn-primary.is-done { background-color: var(--color-green); }
|
||||
|
||||
/* Tabs — sit at the bottom of the results header banner. */
|
||||
.tls-results-header .tls-tabs {
|
||||
@@ -291,11 +278,11 @@
|
||||
flex-wrap: wrap;
|
||||
gap: 0;
|
||||
margin: 12px 0 16px 0;
|
||||
border-bottom: 1px solid #1a1a1a;
|
||||
border-bottom: 1px solid var(--color-dark-grey);
|
||||
}
|
||||
.tls-tab {
|
||||
background: transparent;
|
||||
color: #939393;
|
||||
color: var(--color-light-grey-alt);
|
||||
border: none;
|
||||
border-bottom: 2px solid transparent;
|
||||
padding: 10px 18px 10px 18px;
|
||||
@@ -305,12 +292,12 @@
|
||||
margin-bottom: -1px;
|
||||
}
|
||||
.tls-tab:hover {
|
||||
color: #E0E0E0;
|
||||
color: var(--color-light-grey);
|
||||
}
|
||||
.tls-tab.is-active {
|
||||
color: #FFFFFF;
|
||||
color: var(--color-white);
|
||||
font-weight: 600;
|
||||
border-bottom-color: #E0E0E0;
|
||||
border-bottom-color: var(--color-light-grey);
|
||||
}
|
||||
.tls-tab-panels {
|
||||
padding: 0 clamp(16px, 4vw, 40px) 40px;
|
||||
@@ -332,7 +319,6 @@
|
||||
.tls-section-title {
|
||||
font-size: 14pt;
|
||||
font-weight: 600;
|
||||
color: #FFFFFF;
|
||||
margin: 0 0 10px 0;
|
||||
padding-bottom: 6px;
|
||||
border-bottom: 1px solid #3a3a3a;
|
||||
@@ -346,7 +332,7 @@
|
||||
background-color: #202020;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
color: #E0E0E0;
|
||||
color: var(--color-light-grey);
|
||||
}
|
||||
.tls-table th,
|
||||
.tls-table td {
|
||||
@@ -357,7 +343,7 @@
|
||||
}
|
||||
.tls-table thead th {
|
||||
background-color: #2a2a2a;
|
||||
color: #939393;
|
||||
color: var(--color-light-grey-alt);
|
||||
font-weight: 500;
|
||||
font-size: 10.5pt;
|
||||
text-transform: uppercase;
|
||||
@@ -368,7 +354,6 @@
|
||||
border-bottom: none;
|
||||
}
|
||||
.tls-table code {
|
||||
font-family: "MesloLGS Nerd Font", "MesloLGS NF", "Menlo", "Consolas", monospace;
|
||||
font-size: 10.5pt;
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
@@ -381,7 +366,7 @@
|
||||
.tls-cipher-version {
|
||||
margin: 0 0 6px 0;
|
||||
font-size: 11.5pt;
|
||||
color: #E0E0E0;
|
||||
color: var(--color-light-grey);
|
||||
font-weight: 600;
|
||||
}
|
||||
.tls-cipher-list {
|
||||
@@ -395,9 +380,8 @@
|
||||
gap: 4px 16px;
|
||||
}
|
||||
.tls-cipher-list code {
|
||||
font-family: "MesloLGS Nerd Font", "MesloLGS NF", "Menlo", "Consolas", monospace;
|
||||
font-size: 10pt;
|
||||
color: #E0E0E0;
|
||||
color: var(--color-light-grey);
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
}
|
||||
@@ -421,36 +405,28 @@
|
||||
margin: 0;
|
||||
}
|
||||
.tls-raw code {
|
||||
font-family: "MesloLGS Nerd Font", "MesloLGS NF", "Menlo", "Consolas", monospace;
|
||||
white-space: pre;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* Print stylesheet — "PDFをダウンロード" uses window.print() which
|
||||
* lets the user save the page as a PDF from the browser print dialog.
|
||||
* Hide chrome (tabs, buttons, site header/footer) and expand all tab
|
||||
* panels so the resulting PDF contains every finding.
|
||||
* ------------------------------------------------------------------ */
|
||||
/* Print stylesheet — "PDFをダウンロード" uses window.print(). Hide chrome and
|
||||
* expand all tab panels so the resulting PDF contains every finding. */
|
||||
@media print {
|
||||
html, body {
|
||||
background: #ffffff !important;
|
||||
color: #101010 !important;
|
||||
}
|
||||
/* Hide site-wide chrome and interactive controls. */
|
||||
footer, .tls-tabs, .tls-results-actions,
|
||||
#tls-copy-json, script, .tls-landing-links {
|
||||
display: none !important;
|
||||
}
|
||||
/* Keep the results-header banner but make it paper-friendly. */
|
||||
.tls-results-header {
|
||||
background: transparent !important;
|
||||
border: 0 !important;
|
||||
padding: 0 0 16px 0 !important;
|
||||
border-bottom: 1px solid #cccccc !important;
|
||||
}
|
||||
/* Expand every tab panel so the full report prints. */
|
||||
.tls-tab-panels {
|
||||
padding: 0 !important;
|
||||
}
|
||||
@@ -481,7 +457,6 @@
|
||||
border-bottom: 1px dotted #cccccc;
|
||||
padding: 4px 0;
|
||||
}
|
||||
/* Rank badge keeps its colour via currentColor. */
|
||||
.tls-rank-badge {
|
||||
print-color-adjust: exact;
|
||||
-webkit-print-color-adjust: exact;
|
||||
|
||||
@@ -7,24 +7,15 @@
|
||||
const logEl = document.getElementById("tls-log");
|
||||
const verbEl = document.getElementById("tls-status-verb");
|
||||
|
||||
// Colors for the severity label tag. Order Good→Bad maps to
|
||||
// bright-{green,blue,yellow,red,purple}. Matches SEVERITY_COLORS in
|
||||
// schemas.py so the live log and results page render identically.
|
||||
const SEV_COLOR = {
|
||||
good: "bright-green",
|
||||
normal: "bright-blue",
|
||||
notgood: "bright-yellow",
|
||||
bad: "bright-red",
|
||||
serious: "bright-purple",
|
||||
info: "light-grey-alt",
|
||||
};
|
||||
const SEV_LABEL = {
|
||||
good: "GOOD",
|
||||
normal: "NORMAL",
|
||||
notgood: "NOT GOOD",
|
||||
bad: "BAD",
|
||||
serious: "SERIOUS",
|
||||
info: "INFO",
|
||||
// Severity label tag. Matches SEVERITY_COLORS in schemas.py so the live
|
||||
// log and results page render identically.
|
||||
const SEV = {
|
||||
good: { color: "bright-green", label: "GOOD" },
|
||||
normal: { color: "bright-blue", label: "NORMAL" },
|
||||
notgood: { color: "bright-yellow", label: "NOT GOOD" },
|
||||
bad: { color: "bright-red", label: "BAD" },
|
||||
serious: { color: "bright-purple", label: "SERIOUS" },
|
||||
info: { color: "light-grey-alt", label: "INFO" },
|
||||
};
|
||||
|
||||
let reconnectAttempts = 0;
|
||||
@@ -50,10 +41,10 @@
|
||||
|
||||
const msg = document.createElement("span");
|
||||
msg.className = "tls-log-msg";
|
||||
if (severity && SEV_LABEL[severity] && severity !== "info") {
|
||||
if (severity && SEV[severity] && severity !== "info") {
|
||||
const sev = document.createElement("span");
|
||||
sev.className = "text-" + (SEV_COLOR[severity] || "light-grey") + " font-bold";
|
||||
sev.textContent = SEV_LABEL[severity];
|
||||
sev.className = "text-" + SEV[severity].color + " font-bold";
|
||||
sev.textContent = SEV[severity].label;
|
||||
msg.appendChild(sev);
|
||||
msg.appendChild(document.createTextNode(" "));
|
||||
}
|
||||
|
||||
@@ -11,18 +11,17 @@ from bs4 import BeautifulSoup
|
||||
from markitdown import MarkItDown
|
||||
from datetime import datetime, timezone
|
||||
from contextlib import asynccontextmanager
|
||||
from fastapi import FastAPI, Request, Response, WebSocket, WebSocketDisconnect, HTTPException
|
||||
from fastapi import FastAPI, Request, Response, WebSocket, HTTPException
|
||||
from fastapi.templating import Jinja2Templates
|
||||
from fastapi.responses import PlainTextResponse, JSONResponse, FileResponse, RedirectResponse
|
||||
from jinja2.exceptions import TemplateNotFound
|
||||
from .error import error_page
|
||||
from .database import AccessCounter
|
||||
from .middleware import Middleware, server_version, onion_hostname
|
||||
from .tools.tls_test import TlsJobQueue, TlsTestDB, tls_submit, tls_api_submit, tls_results_context
|
||||
from .tools.tls_test.engine import run_full_scan
|
||||
|
||||
tls_test_db = TlsTestDB()
|
||||
tls_test_queue = TlsJobQueue(tls_test_db, run_full_scan)
|
||||
from .tools.tls_test import (
|
||||
tls_test_db, tls_test_queue,
|
||||
tls_submit, tls_api_submit, tls_results_context, tls_websocket_handler,
|
||||
)
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
@@ -179,40 +178,7 @@ async def tls_test_results_page(request: Request, test_id: str) -> Response:
|
||||
|
||||
@app.websocket("/tools/tls-test/ws/{test_id}")
|
||||
async def tls_test_ws(websocket: WebSocket, test_id: str):
|
||||
job = tls_test_db.get_job(test_id)
|
||||
if not job:
|
||||
await websocket.close(code=4404)
|
||||
return
|
||||
await websocket.accept()
|
||||
tls_test_queue.add_subscriber(test_id, websocket)
|
||||
try:
|
||||
history = tls_test_db.get_progress(test_id)
|
||||
await websocket.send_text(json.dumps({
|
||||
"type": "history",
|
||||
"status": job.get("status"),
|
||||
"target": job.get("target"),
|
||||
"entries": history,
|
||||
}))
|
||||
if job.get("status") == "done":
|
||||
await websocket.send_text(json.dumps({
|
||||
"type": "done",
|
||||
"redirect": f"/tools/tls-test/results/{test_id}/",
|
||||
"rank": job.get("rank"),
|
||||
"score": job.get("score"),
|
||||
}))
|
||||
await websocket.close()
|
||||
return
|
||||
while True:
|
||||
try:
|
||||
await websocket.receive_text()
|
||||
except WebSocketDisconnect:
|
||||
break
|
||||
except WebSocketDisconnect:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
finally:
|
||||
tls_test_queue.remove_subscriber(test_id, websocket)
|
||||
await tls_websocket_handler(websocket, test_id, tls_test_db, tls_test_queue)
|
||||
|
||||
@app.api_route("/api/tools/tls-test/scan", methods=["POST"])
|
||||
async def tls_test_api_scan(request: Request) -> Response:
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
from .runner import TlsJobQueue
|
||||
from .db import TlsTestDB
|
||||
from .views import tls_submit, tls_api_submit, tls_results_context
|
||||
from .engine import run_full_scan
|
||||
from .views import tls_submit, tls_api_submit, tls_results_context, tls_websocket_handler
|
||||
|
||||
__all__ = ["TlsJobQueue", "TlsTestDB", "tls_submit", "tls_api_submit", "tls_results_context"]
|
||||
tls_test_db = TlsTestDB()
|
||||
tls_test_queue = TlsJobQueue(tls_test_db, run_full_scan)
|
||||
|
||||
__all__ = [
|
||||
"TlsJobQueue", "TlsTestDB",
|
||||
"tls_submit", "tls_api_submit", "tls_results_context", "tls_websocket_handler",
|
||||
"tls_test_db", "tls_test_queue",
|
||||
]
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
import json
|
||||
import datetime
|
||||
from fastapi import Request
|
||||
from fastapi import Request, WebSocket, WebSocketDisconnect
|
||||
from fastapi.templating import Jinja2Templates
|
||||
from fastapi.responses import Response
|
||||
from .engine import validate_tls_target
|
||||
@@ -101,3 +102,39 @@ def tls_results_context(job: dict, test_id: str, request: Request, tls_test_db)
|
||||
"log_entries": log_entries,
|
||||
"history": history,
|
||||
}
|
||||
|
||||
|
||||
async def tls_websocket_handler(websocket: WebSocket, test_id: str, tls_test_db, tls_test_queue) -> None:
|
||||
job = tls_test_db.get_job(test_id)
|
||||
if not job:
|
||||
await websocket.close(code=4404)
|
||||
return
|
||||
await websocket.accept()
|
||||
tls_test_queue.add_subscriber(test_id, websocket)
|
||||
try:
|
||||
await websocket.send_text(json.dumps({
|
||||
"type": "history",
|
||||
"status": job.get("status"),
|
||||
"target": job.get("target"),
|
||||
"entries": tls_test_db.get_progress(test_id),
|
||||
}))
|
||||
if job.get("status") == "done":
|
||||
await websocket.send_text(json.dumps({
|
||||
"type": "done",
|
||||
"redirect": f"/tools/tls-test/results/{test_id}/",
|
||||
"rank": job.get("rank"),
|
||||
"score": job.get("score"),
|
||||
}))
|
||||
await websocket.close()
|
||||
return
|
||||
while True:
|
||||
try:
|
||||
await websocket.receive_text()
|
||||
except WebSocketDisconnect:
|
||||
break
|
||||
except WebSocketDisconnect:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
finally:
|
||||
tls_test_queue.remove_subscriber(test_id, websocket)
|
||||
|
||||
Reference in New Issue
Block a user