init: cobol-java migration verification platform v3 (42 tests, JCL module)
This commit is contained in:
+54
-45
@@ -1,80 +1,89 @@
|
||||
"""Web API layer — wraps orchestrator with 202+ polling pattern."""
|
||||
import uuid, json, shutil, sys, os
|
||||
import uuid, json, sys, os
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
from fastapi import FastAPI, UploadFile, File, Form, HTTPException
|
||||
from fastapi.responses import HTMLResponse, JSONResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from fastapi.templating import Jinja2Templates
|
||||
from starlette.requests import Request
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
from config import Config
|
||||
from orchestrator import run_pipeline
|
||||
|
||||
app = FastAPI(title="COBOL→Java Verify")
|
||||
app.mount("/static", StaticFiles(directory=str(Path(__file__).parent / "static")), name="static")
|
||||
templates = Jinja2Templates(directory=str(Path(__file__).parent / "templates"))
|
||||
app = FastAPI(title="COBOL->Java Verify")
|
||||
BASE = Path(__file__).parent
|
||||
app.mount("/static", StaticFiles(directory=str(BASE / "static")), name="static")
|
||||
|
||||
TASKS_DIR = Path("tasks")
|
||||
TASKS_DIR.mkdir(exist_ok=True)
|
||||
UPLOAD_DIR = Path("uploads")
|
||||
UPLOAD_DIR.mkdir(exist_ok=True)
|
||||
MAX_SIZE = 10 * 1024 * 1024 # 10MB
|
||||
TASKS_DIR = Path("tasks"); TASKS_DIR.mkdir(exist_ok=True)
|
||||
UPLOAD_DIR = Path("uploads"); UPLOAD_DIR.mkdir(exist_ok=True)
|
||||
MAX_SIZE = 10 * 1024 * 1024
|
||||
|
||||
|
||||
@app.get("/", response_class=HTMLResponse)
|
||||
async def index(request: Request):
|
||||
return templates.TemplateResponse("upload.html", {"request": request})
|
||||
async def index():
|
||||
return (BASE / "templates" / "upload.html").read_text(encoding="utf-8")
|
||||
|
||||
|
||||
@app.post("/verify")
|
||||
async def verify(
|
||||
copybook: UploadFile = File(...),
|
||||
cobol_src: UploadFile = File(...),
|
||||
java_src: UploadFile = File(...),
|
||||
mapping: UploadFile = File(...),
|
||||
copybook: UploadFile = File(...), cobol_src: UploadFile = File(...),
|
||||
java_src: UploadFile = File(...), mapping: UploadFile = File(...),
|
||||
runner: str = Form("native"),
|
||||
):
|
||||
task_id = str(uuid.uuid4())[:8]
|
||||
task_dir = UPLOAD_DIR / task_id
|
||||
task_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
for f, name in [(copybook, "copybook.cpy"), (cobol_src, "program.cbl"),
|
||||
(java_src, "java"), (mapping, "mapping.yaml")]:
|
||||
task_dir = UPLOAD_DIR / task_id; task_dir.mkdir(parents=True, exist_ok=True)
|
||||
for f, name in [(copybook,"copybook.cpy"),(cobol_src,"program.cbl"),
|
||||
(java_src,"java"),(mapping,"mapping.yaml")]:
|
||||
content = await f.read()
|
||||
if len(content) > MAX_SIZE:
|
||||
raise HTTPException(413, f"{f.filename} exceeds 10MB limit")
|
||||
dest = task_dir / name
|
||||
dest.write_bytes(content)
|
||||
|
||||
task_file = TASKS_DIR / f"{task_id}.json"
|
||||
task_file.write_text(json.dumps({
|
||||
"id": task_id, "status": "queued",
|
||||
"copybook": str(task_dir / "copybook.cpy"),
|
||||
"cobol_src": str(task_dir / "program.cbl"),
|
||||
"java_src": str(task_dir / "java"),
|
||||
"mapping": str(task_dir / "mapping.yaml"),
|
||||
"runner": runner, "created": datetime.now().isoformat()
|
||||
}))
|
||||
|
||||
return JSONResponse({"task_id": task_id, "status": "queued"}, status_code=202)
|
||||
raise HTTPException(413, f"{f.filename} exceeds 10MB")
|
||||
(task_dir / name).write_bytes(content)
|
||||
(TASKS_DIR / f"{task_id}.json").write_text(json.dumps({
|
||||
"id":task_id,"status":"queued","copybook":str(task_dir/"copybook.cpy"),
|
||||
"cobol_src":str(task_dir/"program.cbl"),"java_src":str(task_dir/"java"),
|
||||
"mapping":str(task_dir/"mapping.yaml"),"runner":runner,
|
||||
"created":datetime.now().isoformat()}))
|
||||
return JSONResponse({"task_id":task_id,"status":"queued"}, status_code=202)
|
||||
|
||||
|
||||
@app.get("/status/{task_id}")
|
||||
async def status(task_id: str):
|
||||
tf = TASKS_DIR / f"{task_id}.json"
|
||||
if not tf.exists():
|
||||
raise HTTPException(404, "task not found")
|
||||
if not tf.exists(): raise HTTPException(404, "task not found")
|
||||
data = json.loads(tf.read_text())
|
||||
return JSONResponse({"task_id": task_id, "status": data.get("status", "unknown"),
|
||||
"result": data.get("result")})
|
||||
return JSONResponse({"task_id":task_id,"status":data.get("status","unknown"),
|
||||
"result":data.get("result"),"fields":data.get("fields",[])})
|
||||
|
||||
|
||||
@app.get("/fields/{task_id}")
|
||||
async def fields(task_id: str):
|
||||
tf = TASKS_DIR / f"{task_id}.json"
|
||||
if not tf.exists(): raise HTTPException(404, "task not found")
|
||||
data = json.loads(tf.read_text())
|
||||
return JSONResponse({"task_id":task_id,"fields":data.get("fields",[]),
|
||||
"debug":data.get("debug",{}),
|
||||
"build_log":data.get("build_log","")})
|
||||
|
||||
|
||||
@app.get("/result/{task_id}", response_class=HTMLResponse)
|
||||
async def result(request: Request, task_id: str):
|
||||
async def result(task_id: str):
|
||||
tf = TASKS_DIR / f"{task_id}.json"
|
||||
if not tf.exists():
|
||||
raise HTTPException(404, "task not found")
|
||||
if not tf.exists(): raise HTTPException(404, "task not found")
|
||||
data = json.loads(tf.read_text())
|
||||
return templates.TemplateResponse("result.html", {"request": request, "task": data})
|
||||
html = (BASE / "templates" / "result.html").read_text(encoding="utf-8")
|
||||
html = html.replace("{{ task.id }}", data.get("id", task_id))
|
||||
if data.get("status") == "done" and data.get("result"):
|
||||
r = data["result"]
|
||||
html = html.replace("{{ task.status }}", "done")
|
||||
html = html.replace("{{ task.result.status }}", r.get("status",""))
|
||||
html = html.replace("{{ task.result.program }}", r.get("program",""))
|
||||
html = html.replace("{{ task.result.matched }}", str(r.get("matched",0)))
|
||||
html = html.replace("{{ task.result.mismatched }}", str(r.get("mismatched",0)))
|
||||
html = html.replace("{{ task.result.runner }}", r.get("runner",""))
|
||||
html = html.replace("{{ task.result.duration }}", str(r.get("duration",0)))
|
||||
elif data.get("status") == "error":
|
||||
html = html.replace("{{ task.status }}", "error")
|
||||
html = html.replace("{{ task.result.status }}", data.get("result",""))
|
||||
else:
|
||||
html = html.replace("{{ task.status }}", data.get("status","queued"))
|
||||
return HTMLResponse(html)
|
||||
|
||||
+21
-10
@@ -1,25 +1,36 @@
|
||||
document.getElementById("verify-form").addEventListener("submit", async (e) => {
|
||||
e.preventDefault();
|
||||
const btn = e.target.querySelector("button");
|
||||
const btn = e.target.querySelector("button[type=submit]");
|
||||
btn.disabled = true;
|
||||
btn.textContent = "Submitting...";
|
||||
document.getElementById("status").textContent = "";
|
||||
document.getElementById("result").innerHTML = "";
|
||||
btn.textContent = "$ uploading...";
|
||||
document.getElementById("status-area").innerHTML = "";
|
||||
|
||||
const fd = new FormData(e.target);
|
||||
try {
|
||||
const r = await fetch("/verify", { method: "POST", body: fd });
|
||||
const d = await r.json();
|
||||
if (r.ok) {
|
||||
document.getElementById("status").textContent = "Task " + d.task_id + " queued";
|
||||
document.getElementById("result").innerHTML =
|
||||
'<p><a href="/result/' + d.task_id + '">View result →</a></p>';
|
||||
document.getElementById("status-area").innerHTML = `
|
||||
<div class="status-card pending">
|
||||
<div class="title">● Queued</div>
|
||||
Task <code>${d.task_id}</code> submitted. Worker processing.
|
||||
<div class="matrix"><dt>Runner</dt><dd>${fd.get("runner")}</dd></div>
|
||||
<a class="result-link" href="/result/${d.task_id}">Open result page →</a>
|
||||
</div>`;
|
||||
} else {
|
||||
document.getElementById("status").textContent = "Error: " + (d.detail || "unknown");
|
||||
document.getElementById("status-area").innerHTML = `
|
||||
<div class="status-card error">
|
||||
<div class="title">✗ Error</div>
|
||||
${d.detail || "Upload failed"}
|
||||
</div>`;
|
||||
}
|
||||
} catch (err) {
|
||||
document.getElementById("status").textContent = "Upload failed: " + err.message;
|
||||
document.getElementById("status-area").innerHTML = `
|
||||
<div class="status-card error">
|
||||
<div class="title">✗ Network Error</div>
|
||||
${err.message}
|
||||
</div>`;
|
||||
}
|
||||
btn.disabled = false;
|
||||
btn.textContent = "Verify";
|
||||
btn.textContent = "$ verify";
|
||||
});
|
||||
|
||||
+70
-11
@@ -1,11 +1,70 @@
|
||||
body { font-family: monospace; max-width: 900px; margin: 2rem auto; padding: 0 1rem; color: #333; }
|
||||
h1 { font-size: 1.2rem; }
|
||||
label { display: block; margin: .75rem 0; }
|
||||
input, select { display: block; margin-top: .25rem; }
|
||||
button { margin-top: 1rem; padding: .5rem 1.5rem; cursor: pointer; font-size: 1rem; }
|
||||
#status { margin-top: 1rem; font-weight: bold; }
|
||||
#result { margin-top: 1rem; }
|
||||
pre { background: #f0f0f0; padding: 1rem; border-radius: 4px; }
|
||||
.pass { border-left: 4px solid #4caf50; }
|
||||
.fail { border-left: 4px solid #f44336; }
|
||||
.container a { display: inline-block; margin-top: 1rem; }
|
||||
:root {
|
||||
--bg: #0a0e14;
|
||||
--panel: #12171f;
|
||||
--border: #1f2937;
|
||||
--text: #b2becd;
|
||||
--dim: #5c6e80;
|
||||
--accent: #39bae6;
|
||||
--green: #7fd962;
|
||||
--red: #f26d78;
|
||||
--yellow: #ffad66;
|
||||
--font: "SF Mono", "Fira Code", "Cascadia Code", Consolas, monospace;
|
||||
}
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body { font-family: var(--font); background: var(--bg); color: var(--text); line-height: 1.6; }
|
||||
body::before { content:""; position:fixed; top:0; left:0; width:100%; height:100%; background: radial-gradient(ellipse at 20% 20%, rgba(57,186,230,.03) 0%, transparent 50%), radial-gradient(ellipse at 80% 80%, rgba(127,217,98,.02) 0%, transparent 50%); pointer-events:none; z-index:0; }
|
||||
.container { max-width: 680px; margin: 0 auto; padding: 3rem 1.5rem; position:relative; z-index:1; }
|
||||
/* Header */
|
||||
header { margin-bottom: 2.5rem; border-bottom: 1px solid var(--border); padding-bottom: 1.5rem; }
|
||||
.badge { display:inline-block; padding:.15rem .6rem; background:rgba(57,186,230,.12); color:var(--accent); border-radius:3px; font-size:.7rem; text-transform:uppercase; letter-spacing:.08em; margin-bottom:.75rem; }
|
||||
h1 { font-size:1.3rem; font-weight:400; color:#e6edf3; line-height:1.3; }
|
||||
h1 span { color: var(--accent); }
|
||||
.tagline { font-size:.8rem; color:var(--dim); margin-top:.4rem; }
|
||||
/* Sections */
|
||||
section { background:var(--panel); border:1px solid var(--border); border-radius:8px; padding:1.5rem; margin-bottom:1rem; }
|
||||
section h2 { font-size:.8rem; color:var(--dim); text-transform:uppercase; letter-spacing:.06em; margin-bottom:1rem; font-weight:400; }
|
||||
/* Form */
|
||||
.form-grid { display:grid; grid-template-columns:1fr 1fr; gap:.75rem; }
|
||||
.form-grid .full { grid-column:1/-1; }
|
||||
label { font-size:.75rem; color:var(--dim); }
|
||||
label strong { display:block; color:var(--text); font-weight:400; margin-bottom:.25rem; font-size:.8rem; }
|
||||
input[type="file"], select { width:100%; padding:.55rem .7rem; background:var(--bg); border:1px solid var(--border); border-radius:6px; color:var(--text); font-family:inherit; font-size:.8rem; margin-top:.15rem; transition:border-color .2s; }
|
||||
input[type="file"]:hover, select:hover { border-color: var(--accent); }
|
||||
input[type="file"]::file-selector-button { background:var(--border); color:var(--text); border:none; padding:.35rem .8rem; border-radius:4px; margin-right:.5rem; cursor:pointer; font-family:inherit; font-size:.75rem; }
|
||||
select { cursor:pointer; background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='%235c6e80'%3E%3Cpath d='M6 8L2 4h8z'/%3E%3C/svg%3E"); background-repeat:no-repeat; background-position:right .5rem center; appearance:none; padding-right:2rem; }
|
||||
/* Buttons */
|
||||
.actions { display:flex; gap:.5rem; margin-top:1.25rem; }
|
||||
.btn { padding:.6rem 1.25rem; border-radius:6px; font-family:inherit; font-size:.8rem; cursor:pointer; border:none; transition: all .2s; }
|
||||
.btn-primary { background:var(--accent); color:var(--bg); }
|
||||
.btn-primary:hover { background:#4fc8f0; }
|
||||
.btn-primary:disabled { background:var(--border); color:var(--dim); cursor:not-allowed; }
|
||||
.btn-secondary { background:transparent; color:var(--dim); border:1px solid var(--border); }
|
||||
.btn-secondary:hover { color:var(--text); border-color:var(--dim); }
|
||||
/* Status & Results */
|
||||
#status-area { margin-top:1rem; }
|
||||
.status-card { background:var(--panel); border:1px solid var(--border); border-radius:8px; padding:1rem 1.5rem; font-size:.8rem; }
|
||||
.status-card.success { border-left:3px solid var(--green); }
|
||||
.status-card.pending { border-left:3px solid var(--yellow); }
|
||||
.status-card.error { border-left:3px solid var(--red); }
|
||||
.status-card .title { font-weight:600; margin-bottom:.25rem; }
|
||||
.status-card.success .title { color:var(--green); }
|
||||
.status-card.pending .title { color:var(--yellow); }
|
||||
.status-card.error .title { color:var(--red); }
|
||||
.matrix { display:grid; grid-template-columns:auto 1fr; gap:.3rem 1.5rem; margin:.75rem 0; font-size:.8rem; }
|
||||
.matrix dt { color:var(--dim); }
|
||||
.matrix dd { color:var(--text); }
|
||||
.divider { border-top:1px solid var(--border); margin:1rem 0; }
|
||||
.result-link { display:inline-block; margin-top:.5rem; color:var(--accent); text-decoration:none; font-size:.8rem; }
|
||||
.result-link:hover { text-decoration:underline; }
|
||||
/* Footer */
|
||||
footer { margin-top:3rem; padding-top:1rem; border-top:1px solid var(--border); font-size:.7rem; color:var(--dim); display:flex; justify-content:space-between; }
|
||||
footer a { color:var(--dim); text-decoration:none; }
|
||||
footer a:hover { color:var(--text); }
|
||||
/* Field table */
|
||||
table { width:100%; border-collapse:collapse; font-size:.8rem; }
|
||||
th { text-align:left; color:var(--dim); font-weight:400; padding:.5rem .75rem; border-bottom:1px solid var(--border); }
|
||||
td { padding:.45rem .75rem; border-bottom:1px solid rgba(31,41,55,.5); }
|
||||
tr.pass td:first-child { border-left:3px solid var(--green); }
|
||||
tr.tolerated td:first-child { border-left:3px solid var(--yellow); }
|
||||
tr.fail td:first-child { border-left:3px solid var(--red); }
|
||||
pre { background:var(--bg); border:1px solid var(--border); border-radius:6px; padding:1rem; font-family:inherit; font-size:.8rem; overflow-x:auto; margin-top:.5rem; }
|
||||
|
||||
+89
-17
@@ -1,27 +1,99 @@
|
||||
<!DOCTYPE html>
|
||||
<html><head><meta charset="utf-8"><title>Result - {{ task.id }}</title>
|
||||
<link rel="stylesheet" href="/static/style.css"></head><body>
|
||||
<html lang="en">
|
||||
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Result — {{ task.id }}</title>
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Verification Result</h1>
|
||||
{% if task.status == "done" and task.result %}
|
||||
<pre class="pass">Status: {{ task.result.status }}
|
||||
Program: {{ task.result.program }}
|
||||
Matched: {{ task.result.matched }} | Mismatched: {{ task.result.mismatched }}
|
||||
Runner: {{ task.result.runner }} | Duration: {{ task.result.duration }}s</pre>
|
||||
{% elif task.status == "error" %}
|
||||
<pre class="fail">{{ task.result }}</pre>
|
||||
{% else %}
|
||||
<div id="poll-status">Status: {{ task.status }} — polling...</div>
|
||||
|
||||
<header>
|
||||
<div class="badge">Verification Result</div>
|
||||
<h1>{{ task.id }}</h1>
|
||||
<div class="tagline">Status: <span class="poll-status">loading...</span></div>
|
||||
</header>
|
||||
|
||||
<section>
|
||||
<h2>Summary</h2>
|
||||
<div class="matrix">
|
||||
<dt>Status</dt><dd>{{ task.result.status }}</dd>
|
||||
<dt>Program</dt><dd>{{ task.result.program }}</dd>
|
||||
<dt>Matched</dt><dd>{{ task.result.matched }}</dd>
|
||||
<dt>Mismatched</dt><dd>{{ task.result.mismatched }}</dd>
|
||||
<dt>Runner</dt><dd>{{ task.result.runner }}</dd>
|
||||
<dt>Duration</dt><dd>{{ task.result.duration }}s</dd>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Field Results</h2>
|
||||
<div id="fields-table"></div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Pipeline Details</h2>
|
||||
<div id="debug-section"></div>
|
||||
</section>
|
||||
|
||||
<div class="divider"></div>
|
||||
<a href="/" class="btn btn-secondary">← New Verification</a>
|
||||
|
||||
<footer>
|
||||
<span>verify-cli v0.2.0</span>
|
||||
<span><a href="#">Docs</a> · <a href="#">Architecture</a></span>
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
<script>
|
||||
const id = "{{ task.id }}";
|
||||
setInterval(async () => {
|
||||
const r = await fetch("/status/" + id);
|
||||
const d = await r.json();
|
||||
document.getElementById("poll-status").textContent = "Status: " + d.status;
|
||||
document.querySelector(".poll-status").textContent = d.status;
|
||||
if (d.status === "done" || d.status === "error") location.reload();
|
||||
}, 3000);
|
||||
|
||||
// Load per-field results + debug
|
||||
(async () => {
|
||||
const r = await fetch("/fields/" + id);
|
||||
const d = await r.json();
|
||||
if (d.fields && d.fields.length) {
|
||||
const rows = d.fields.map(f => {
|
||||
const cls = f.status === "PASS" ? "pass" : f.status === "TOLERATED" ? "tolerated" : "fail";
|
||||
return `<tr class="${cls}"><td>${f.name}</td><td>${f.status}</td><td>${f.cobol||""}</td><td>${f.java||""}</td><td>${f.suggestion||""}</td></tr>`;
|
||||
}).join("");
|
||||
document.getElementById("fields-table").innerHTML =
|
||||
`<table><tr><th>Field</th><th>Status</th><th>COBOL</th><th>Java</th><th>Suggestion</th></tr>${rows}</table>`;
|
||||
}
|
||||
// Render debug info
|
||||
const dbg = d.debug || {};
|
||||
let html = "";
|
||||
if (dbg.field_tree) {
|
||||
const treeRows = dbg.field_tree.map(f =>
|
||||
`<tr><td>L${f.level}</td><td>${f.name}</td><td>${f.pic}</td><td>${f.usage}</td><td>${f.offset}</td><td>${f.length}</td></tr>`).join("");
|
||||
html += `<h3 style="color:var(--dim);font-size:.75rem;letter-spacing:.05em;margin:1rem 0 .5rem">COPYBOOK FieldTree</h3>
|
||||
<table><tr><th>Lv</th><th>Name</th><th>PIC</th><th>Usage</th><th>Off</th><th>Len</th></tr>${treeRows}</table>`;
|
||||
}
|
||||
if (dbg.test_cases) {
|
||||
const tcList = dbg.test_cases.map(tc =>
|
||||
`<tr><td>${tc.id}</td><td>${JSON.stringify(tc.fields)}</td><td>${(tc.targets||[]).join(", ")}</td></tr>`).join("");
|
||||
html += `<h3 style="color:var(--dim);font-size:.75rem;letter-spacing:.05em;margin:1rem 0 .5rem">Test Data (${dbg.test_cases.length} cases)</h3>
|
||||
<table><tr><th>ID</th><th>Fields</th><th>Coverage</th></tr>${tcList}</table>`;
|
||||
}
|
||||
if (dbg.spark_config) {
|
||||
html += `<h3 style="color:var(--dim);font-size:.75rem;letter-spacing:.05em;margin:1rem 0 .5rem">Spark Config</h3>
|
||||
<pre>${dbg.spark_config.records} records via key_varied replication</pre>`;
|
||||
}
|
||||
if (dbg.cobol_build) {
|
||||
html += `<h3 style="color:var(--dim);font-size:.75rem;letter-spacing:.05em;margin:1rem 0 .5rem">COBOL Compile${dbg.cobol_build.ok?"":" (FAILED)"}</h3>
|
||||
<pre>${dbg.cobol_build.log||"(no output)"}</pre>`;
|
||||
}
|
||||
if (dbg.java_build) {
|
||||
html += `<h3 style="color:var(--dim);font-size:.75rem;letter-spacing:.05em;margin:1rem 0 .5rem">Java Build${dbg.java_build.ok?"":" (FAILED)"}</h3>
|
||||
<pre>${dbg.java_build.log||"(no output)"}</pre>`;
|
||||
}
|
||||
if (html) document.getElementById("debug-section").innerHTML = html;
|
||||
})();
|
||||
</script>
|
||||
{% endif %}
|
||||
<a href="/">← New verification</a>
|
||||
</div>
|
||||
</body></html>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+39
-14
@@ -1,18 +1,43 @@
|
||||
<!DOCTYPE html>
|
||||
<html><head><meta charset="utf-8"><title>COBOL→Java Verify</title>
|
||||
<link rel="stylesheet" href="/static/style.css"></head><body>
|
||||
<html lang="en">
|
||||
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>COBOL → Java Migration Verification</title>
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>COBOL→Java Migration Verification</h1>
|
||||
<form id="verify-form" enctype="multipart/form-data">
|
||||
<label>COPYBOOK:<input type="file" name="copybook" accept=".cpy,.cbl,.copy" required></label>
|
||||
<label>COBOL source:<input type="file" name="cobol_src" accept=".cbl" required></label>
|
||||
<label>Java source (dir with pom.xml):<input type="file" name="java_src" webkitdirectory required></label>
|
||||
<label>Mapping YAML:<input type="file" name="mapping" accept=".yaml,.yml" required></label>
|
||||
<label>Runner:<select name="runner"><option value="native">Native Java</option><option value="spark">Spark Java</option></select></label>
|
||||
<button type="submit">Verify</button>
|
||||
</form>
|
||||
<div id="status"></div>
|
||||
<div id="result"></div>
|
||||
|
||||
<header>
|
||||
<div class="badge">Developer Tool</div>
|
||||
<h1><span>verify</span> — COBOL to Java/Spark Migration</h1>
|
||||
<div class="tagline">Automated field-level verification for COBOL→Java migration pipelines</div>
|
||||
</header>
|
||||
|
||||
<section>
|
||||
<h2>Upload Sources</h2>
|
||||
<form id="verify-form" enctype="multipart/form-data">
|
||||
<div class="form-grid">
|
||||
<label><strong>COPYBOOK</strong>.cpy / .cbl / .copy<input type="file" name="copybook" accept=".cpy,.cbl,.copy" required></label>
|
||||
<label><strong>COBOL Source</strong>.cbl<input type="file" name="cobol_src" accept=".cbl" required></label>
|
||||
<label><strong>Mapping</strong>.yaml / .yml<input type="file" name="mapping" accept=".yaml,.yml" required></label>
|
||||
<label><strong>Runner</strong><select name="runner"><option value="native">Native Java</option><option value="spark">Spark Java</option></select></label>
|
||||
<label class="full"><strong>Java Source</strong>directory with pom.xml<input type="file" name="java_src" webkitdirectory required></label>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button type="submit" class="btn btn-primary">$ verify</button>
|
||||
<button type="reset" class="btn btn-secondary">Clear</button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<div id="status-area"></div>
|
||||
|
||||
<footer>
|
||||
<span>verify-cli v0.2.0</span>
|
||||
<span><a href="#">Docs</a> · <a href="#">Architecture</a></span>
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
<script src="/static/script.js"></script>
|
||||
</body></html>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -32,12 +32,20 @@ def main():
|
||||
vr = run_pipeline(cfg, data["copybook"], data["cobol_src"],
|
||||
data["java_src"], data["mapping"])
|
||||
|
||||
fields = [{"name":fr.field_name,"status":fr.status,
|
||||
"cobol":fr.cobol_value,"java":fr.java_value,
|
||||
"suggestion":fr.suggestion} for fr in vr.field_results]
|
||||
|
||||
data["status"] = "done"
|
||||
data["fields"] = fields
|
||||
data["debug"] = vr.debug
|
||||
data["result"] = {
|
||||
"program": vr.program, "status": vr.status,
|
||||
"matched": vr.fields_matched, "mismatched": vr.fields_mismatched,
|
||||
"duration": vr.duration_s, "runner": vr.runner,
|
||||
}
|
||||
if vr.report_path and "BLOCKED" in vr.status:
|
||||
data["build_log"] = vr.report_path
|
||||
tf.write_text(json.dumps(data))
|
||||
|
||||
except Exception as e:
|
||||
|
||||
Reference in New Issue
Block a user