Build Flask WAF log converter app
This commit is contained in:
38
app/templates/base.html
Normal file
38
app/templates/base.html
Normal file
@@ -0,0 +1,38 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>WAF Log Converter</title>
|
||||
<link
|
||||
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
|
||||
rel="stylesheet"
|
||||
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH"
|
||||
crossorigin="anonymous"
|
||||
>
|
||||
</head>
|
||||
<body class="bg-body-tertiary">
|
||||
<main class="container py-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-10">
|
||||
<div class="mb-4">
|
||||
<h1 class="display-6 fw-semibold">WAF Log Converter</h1>
|
||||
<p class="text-secondary mb-0">
|
||||
Upload a UTF-8 WAF log file and export a filtered report as readable text or CSV.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
{% for category, message in messages %}
|
||||
<div class="alert alert-{{ category }}" role="alert">{{ message }}</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
100
app/templates/index.html
Normal file
100
app/templates/index.html
Normal file
@@ -0,0 +1,100 @@
|
||||
{% extends "base.html" %}
|
||||
{% set form = form or none %}
|
||||
{% block content %}
|
||||
<div class="card shadow-sm border-0">
|
||||
<div class="card-body p-4">
|
||||
<form method="post" action="{{ url_for('main.convert') }}" enctype="multipart/form-data" novalidate>
|
||||
<div class="mb-4">
|
||||
<label for="log_file" class="form-label fw-semibold">Log file</label>
|
||||
<input class="form-control" id="log_file" name="log_file" type="file" required>
|
||||
<div class="form-text">Each line must contain one record using shell-like key/value tokens.</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="col-md-3">
|
||||
<label for="mode" class="form-label">Mode</label>
|
||||
<select class="form-select" id="mode" name="mode">
|
||||
<option value="vendor" {% if form and form.mode == "vendor" %}selected{% endif %}>Vendor</option>
|
||||
<option value="full" {% if form and form.mode == "full" %}selected{% endif %}>Full</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label for="output_format" class="form-label">Format</label>
|
||||
<select class="form-select" id="output_format" name="output_format">
|
||||
<option value="text" {% if form and form.output_format == "text" %}selected{% endif %}>Text</option>
|
||||
<option value="csv" {% if form and form.output_format == "csv" %}selected{% endif %}>CSV</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label for="sort_by" class="form-label">Sort by</label>
|
||||
<select class="form-select" id="sort_by" name="sort_by">
|
||||
<option value="datetime" {% if not form or form.sort_by == "datetime" %}selected{% endif %}>Datetime</option>
|
||||
<option value="severity" {% if form and form.sort_by == "severity" %}selected{% endif %}>Severity</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label for="order" class="form-label">Order</label>
|
||||
<select class="form-select" id="order" name="order">
|
||||
<option value="asc" {% if not form or form.order == "asc" %}selected{% endif %}>Ascending</option>
|
||||
<option value="desc" {% if form and form.order == "desc" %}selected{% endif %}>Descending</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="my-4">
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label for="policy_cs" class="form-label">Policy filter, case-sensitive</label>
|
||||
<input
|
||||
class="form-control"
|
||||
id="policy_cs"
|
||||
name="policy_cs"
|
||||
type="text"
|
||||
value="{{ form.policy_cs if form else '' }}"
|
||||
>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="policy_ci" class="form-label">Policy filter, case-insensitive</label>
|
||||
<input
|
||||
class="form-control"
|
||||
id="policy_ci"
|
||||
name="policy_ci"
|
||||
type="text"
|
||||
value="{{ form.policy_ci if form else '' }}"
|
||||
>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="severity_cs" class="form-label">Severity filter, case-sensitive</label>
|
||||
<input
|
||||
class="form-control"
|
||||
id="severity_cs"
|
||||
name="severity_cs"
|
||||
type="text"
|
||||
value="{{ form.severity_cs if form else '' }}"
|
||||
>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="severity_ci" class="form-label">Severity filter, case-insensitive</label>
|
||||
<input
|
||||
class="form-control"
|
||||
id="severity_ci"
|
||||
name="severity_ci"
|
||||
type="text"
|
||||
value="{{ form.severity_ci if form else '' }}"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-light border mt-4 mb-0" role="note">
|
||||
Use only one policy filter and one severity filter at a time. Matching happens as a partial substring.
|
||||
</div>
|
||||
|
||||
<div class="mt-4 d-flex gap-2">
|
||||
<button class="btn btn-primary" type="submit">Convert log</button>
|
||||
<button class="btn btn-outline-secondary" type="reset">Reset</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
45
app/templates/result.html
Normal file
45
app/templates/result.html
Normal file
@@ -0,0 +1,45 @@
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
<div class="row g-4">
|
||||
<div class="col-lg-4">
|
||||
<div class="card shadow-sm border-0 h-100">
|
||||
<div class="card-body">
|
||||
<h2 class="h4">Result summary</h2>
|
||||
<dl class="row mb-4">
|
||||
<dt class="col-sm-5">Parsed records</dt>
|
||||
<dd class="col-sm-7">{{ parsed_count }}</dd>
|
||||
<dt class="col-sm-5">Output records</dt>
|
||||
<dd class="col-sm-7">{{ filtered_count }}</dd>
|
||||
<dt class="col-sm-5">Mode</dt>
|
||||
<dd class="col-sm-7 text-capitalize">{{ mode }}</dd>
|
||||
<dt class="col-sm-5">Format</dt>
|
||||
<dd class="col-sm-7 text-uppercase">{{ output_format }}</dd>
|
||||
<dt class="col-sm-5">Sort</dt>
|
||||
<dd class="col-sm-7">{{ sort_by }} / {{ order }}</dd>
|
||||
</dl>
|
||||
|
||||
<div class="d-grid gap-2">
|
||||
<a class="btn btn-primary" href="{{ url_for('main.download', result_id=result_id) }}">
|
||||
Download export
|
||||
</a>
|
||||
<a class="btn btn-outline-secondary" href="{{ url_for('main.index') }}">
|
||||
Convert another file
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-8">
|
||||
<div class="card shadow-sm border-0">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h2 class="h4 mb-0">Preview</h2>
|
||||
<span class="badge text-bg-secondary">Showing up to {{ record_count if record_count < 5 else 5 }} records</span>
|
||||
</div>
|
||||
<pre class="bg-dark-subtle p-3 rounded small mb-0" style="white-space: pre-wrap;">{{ preview_text }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user