refactor ciagl dalszy
This commit is contained in:
@@ -1,87 +1,87 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang="en" data-theme="dark">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Error {{ error.code }}</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg-color: #1a1a1a;
|
||||
--card-bg: #2d2d2d;
|
||||
--text-color: #e0e0e0;
|
||||
--accent: #007bff;
|
||||
--border-color: #404040;
|
||||
--error-color: #ff4444;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', system-ui, sans-serif;
|
||||
background-color: var(--bg-color);
|
||||
color: var(--text-color);
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.error-container {
|
||||
max-width: 600px;
|
||||
padding: 40px;
|
||||
background: var(--card-bg);
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--border-color);
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--error-color);
|
||||
font-size: 3.5em;
|
||||
margin: 0 0 20px 0;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 1.2em;
|
||||
margin: 10px 0;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--accent);
|
||||
text-decoration: none;
|
||||
margin-top: 20px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.error-container {
|
||||
padding: 25px;
|
||||
margin: 15px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2.5em;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 1em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Error {{ error.code or 500 }}</title>
|
||||
<meta name="theme-color" content="#0f1115" />
|
||||
<link rel="preload" href="{{ url_for('static', filename='css/main.css') }}" as="style">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/main.css') }}">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="error-container">
|
||||
<h1>Error {{ error.code }}</h1>
|
||||
<p>{{ error.description }}</p>
|
||||
<a href="/">← Return to Home Page</a>
|
||||
</div>
|
||||
<header class="site-header">
|
||||
<div class="brand">
|
||||
<svg aria-hidden="true" width="24" height="24" viewBox="0 0 24 24">
|
||||
<path d="M4 4h16v4H4zM4 10h10v4H4zM4 16h16v4H4z" fill="currentColor" />
|
||||
</svg>
|
||||
<span>Hosts Converter</span>
|
||||
</div>
|
||||
<nav class="actions">
|
||||
<button class="btn ghost" type="button" data-action="toggle-theme" aria-label="Toggle theme">🌓</button>
|
||||
<a class="btn primary" href="/" rel="nofollow">Home</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
<section class="card error-card">
|
||||
<div class="error-hero">
|
||||
<div class="error-illustration" aria-hidden="true">⚠️</div>
|
||||
|
||||
<div class="error-main">
|
||||
<div class="status-badge">Error {{ error.code or 500 }}</div>
|
||||
<h1 class="error-title">
|
||||
{% if (error.code or 500) == 400 %}Bad request
|
||||
{% elif (error.code or 500) == 403 %}Forbidden
|
||||
{% elif (error.code or 500) == 404 %}Not found
|
||||
{% elif (error.code or 500) == 413 %}Payload too large
|
||||
{% elif (error.code or 500) == 415 %}Unsupported media type
|
||||
{% elif (error.code or 500) == 500 %}Internal server error
|
||||
{% else %}Something went wrong
|
||||
{% endif %}
|
||||
</h1>
|
||||
<p class="muted">{{ (error.description|string)|e }}</p>
|
||||
|
||||
<div class="error-actions">
|
||||
<button class="btn" type="button" data-action="try-again">Try again</button>
|
||||
<a class="btn outline" href="/" rel="nofollow">Go home</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<details class="error-details">
|
||||
<summary>
|
||||
<span class="summary-title">Error details</span>
|
||||
<span class="summary-hint">click to expand</span>
|
||||
</summary>
|
||||
<div class="details-body">
|
||||
<pre id="error-dump" class="mono">
|
||||
code: {{ error.code or 500 }}
|
||||
message: {{ (error.description|string) }}
|
||||
path: {{ request.path if request else '/' }}
|
||||
method: {{ request.method if request else 'GET' }}
|
||||
user_ip: {{ request.remote_addr if request else '' }}
|
||||
user_agent: {{ request.headers.get('User-Agent') if request else '' }}
|
||||
</pre>
|
||||
<div class="details-actions">
|
||||
<button class="btn tiny" type="button" data-action="copy-text"
|
||||
data-target="#error-dump">Copy</button>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer class="site-footer">
|
||||
<div>© 2025 <a href="https://www.linuxiarz.pl" target="_blank" rel="noopener">linuxiarz.pl</a></div>
|
||||
<div class="meta">Your IP: <strong>{{ request.remote_addr if request else '' }}</strong></div>
|
||||
</footer>
|
||||
|
||||
<div id="toast" role="status" aria-live="polite" aria-atomic="true"></div>
|
||||
|
||||
<script defer src="{{ url_for('static', filename='js/main.js') }}"></script>
|
||||
<script defer src="{{ url_for('static', filename='js/error.js') }}"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
</html>
|
@@ -1,292 +1,152 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang="en" data-theme="dark">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Hosts Converter</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg-color: #1a1a1a;
|
||||
--card-bg: #2d2d2d;
|
||||
--text-color: #e0e0e0;
|
||||
--accent: #007bff;
|
||||
--accent-light: #4da6ff;
|
||||
--border-color: #404040;
|
||||
--link-color: #4da6ff;
|
||||
}
|
||||
|
||||
[data-theme="light"] {
|
||||
--bg-color: #f5f5f5;
|
||||
--card-bg: #ffffff;
|
||||
--text-color: #333333;
|
||||
--border-color: #dddddd;
|
||||
--link-color: #0066cc;
|
||||
--accent: #0066cc;
|
||||
--accent-light: #007bff;
|
||||
}
|
||||
|
||||
* {
|
||||
transition: background-color 0.3s, color 0.3s;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', system-ui, sans-serif;
|
||||
background-color: var(--bg-color);
|
||||
color: var(--text-color);
|
||||
max-width: 800px;
|
||||
margin: 20px auto;
|
||||
padding: 20px;
|
||||
line-height: 1.6;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.theme-toggle {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 20px;
|
||||
padding: 8px 15px;
|
||||
cursor: pointer;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--accent);
|
||||
margin-bottom: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
form {
|
||||
background: var(--card-bg);
|
||||
padding: 25px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin: 15px 0;
|
||||
padding: 0 15px;
|
||||
}
|
||||
|
||||
input[type="text"] {
|
||||
width: calc(100% - 30px);
|
||||
padding: 10px 15px;
|
||||
margin: 8px 0;
|
||||
background: var(--bg-color);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
button {
|
||||
background: linear-gradient(135deg, var(--accent), var(--accent-light));
|
||||
color: white;
|
||||
padding: 12px 25px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
margin: 0 15px;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.result-box {
|
||||
background: var(--card-bg);
|
||||
padding: 20px;
|
||||
border-radius: 12px;
|
||||
margin: 25px 15px;
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.recent-links {
|
||||
margin: 35px 15px 0;
|
||||
padding: 25px 15px 0;
|
||||
border-top: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.link-item {
|
||||
background: var(--card-bg);
|
||||
padding: 15px;
|
||||
margin: 12px 0;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.link-item:hover {
|
||||
transform: translateX(5px);
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.timestamp {
|
||||
color: #888;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--link-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
footer {
|
||||
text-align: center;
|
||||
margin-top: 40px;
|
||||
padding: 20px;
|
||||
border-top: 1px solid var(--border-color);
|
||||
color: #888;
|
||||
}
|
||||
|
||||
footer a {
|
||||
color: var(--link-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
footer a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
body {
|
||||
padding: 15px;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
form {
|
||||
padding: 15px 0;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
input[type="text"] {
|
||||
width: calc(100% - 20px);
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
button {
|
||||
width: calc(100% - 20px);
|
||||
padding: 15px;
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
.result-box {
|
||||
margin: 25px 10px;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.recent-links {
|
||||
margin: 35px 10px 0;
|
||||
padding: 25px 10px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.copy-btn::after {
|
||||
content: "Copied!";
|
||||
position: absolute;
|
||||
background: var(--card-bg);
|
||||
color: var(--text-color);
|
||||
padding: 5px 10px;
|
||||
border-radius: 4px;
|
||||
right: -80px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
.copy-btn.copied::after {
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
||||
<meta name="theme-color" content="#0f1115" />
|
||||
<link rel="preload" href="{{ url_for('static', filename='css/main.css') }}" as="style">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/main.css') }}">
|
||||
</head>
|
||||
<body data-theme="dark">
|
||||
<button class="theme-toggle" onclick="toggleTheme()">🌓 Toggle Theme</button>
|
||||
|
||||
<h1>Hosts File Converter</h1>
|
||||
|
||||
<form method="GET" action="/">
|
||||
<div class="form-group">
|
||||
<label>URL to hosts file:</label>
|
||||
<input type="text" name="url" required
|
||||
placeholder="ex. https://paulgb.github.io/BarbBlock/blacklists/hosts-file.txt">
|
||||
<body>
|
||||
<header class="site-header">
|
||||
<div class="brand">
|
||||
<svg aria-hidden="true" width="24" height="24" viewBox="0 0 24 24">
|
||||
<path d="M4 4h16v4H4zM4 10h10v4H4zM4 16h16v4H4z" fill="currentColor" />
|
||||
</svg>
|
||||
<span>Hosts Converter</span>
|
||||
</div>
|
||||
<nav class="actions">
|
||||
<button class="btn ghost" type="button" data-action="toggle-theme" aria-label="Toggle theme">🌓</button>
|
||||
<a class="btn primary" href="https://www.linuxiarz.pl" target="_blank" rel="noopener">linuxiarz.pl</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Target IP:</label>
|
||||
<input type="text" name="ip" pattern="\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}"
|
||||
value="195.187.6.34" required>
|
||||
</div>
|
||||
|
||||
<button type="submit">Generate convert link</button>
|
||||
</form>
|
||||
|
||||
{% if generated_link %}
|
||||
<div class="result-box">
|
||||
<h3>Link to MikroTik/Adguard:</h3>
|
||||
<input type="text" value="{{ generated_link }}" readonly>
|
||||
<button class="copy-btn" onclick="copyToClipboard(this)">Copy link</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="recent-links">
|
||||
<h3>Last converts:</h3>
|
||||
{% if recent_links %}
|
||||
{% for link_data in recent_links %}
|
||||
<div class="link-item">
|
||||
<div class="timestamp">{{ link_data[0]|datetimeformat }}</div>
|
||||
<a href="/convert?url={{ link_data[1]|urlencode }}&ip={{ link_data[2] }}" target="_blank">
|
||||
{{ link_data[1] }} → {{ link_data[2] }}
|
||||
</a>
|
||||
<main class="container">
|
||||
<section class="card hero">
|
||||
<div class="hero-text">
|
||||
<h1>Convert adblock lists to MikroTik / hosts</h1>
|
||||
<p class="muted">Paste a list URL (AdGuard/uBlock/hosts/dnsmasq), pick the target IP, and generate a
|
||||
<kbd>/convert</kbd> link.
|
||||
</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<p>Empty..</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="hero-cta">
|
||||
<a class="btn primary large" href="#form">Start</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
© 2025 <a href="https://www.linuxiarz.pl" target="_blank">linuxiarz.pl</a> - All rights reserved <br>
|
||||
Your IP address: <strong>{{ client_ip }}</strong> | Your User Agent: <strong>{{ user_agent }}</strong>
|
||||
<section id="form" class="card form-card" aria-labelledby="form-title">
|
||||
<h2 id="form-title">Link generator</h2>
|
||||
|
||||
<form method="GET" action="/" novalidate>
|
||||
<div class="grid">
|
||||
<div class="form-group col-12">
|
||||
<label for="url-input">URL to hosts/adblock list</label>
|
||||
<input id="url-input" type="url" name="url" required placeholder="https://example.com/list.txt"
|
||||
inputmode="url" autocomplete="url" aria-describedby="url-help" autofocus>
|
||||
<small id="url-help" class="hint"> Supported formats: <code>||domain^</code>,
|
||||
<code>address=/domain/</code>, hosts files, plain domains.</small>
|
||||
<div class="error" data-error-for="url-input"></div>
|
||||
</div>
|
||||
|
||||
<div class="form-group col-6">
|
||||
<label for="ip-input">Target IP</label>
|
||||
<input id="ip-input" type="text" name="ip" pattern="^\d{1,3}(?:\.\d{1,3}){3}$"
|
||||
value="195.187.6.34" required inputmode="numeric" autocomplete="off"
|
||||
aria-describedby="ip-help">
|
||||
<small id="ip-help" class="hint">Common choices: <code>0.0.0.0</code>, <code>127.0.0.1</code>,
|
||||
or your device IP.</small>
|
||||
<div class="error" data-error-for="ip-input"></div>
|
||||
</div>
|
||||
|
||||
<div class="form-group col-6">
|
||||
<label for="ip-preset">Presets</label>
|
||||
<select id="ip-preset" class="select">
|
||||
<option value="">— choose preset —</option>
|
||||
<option value="0.0.0.0">0.0.0.0 (blackhole)</option>
|
||||
<option value="127.0.0.1">127.0.0.1 (localhost)</option>
|
||||
<option value="195.187.6.34">195.187.6.34 (current)</option>
|
||||
<option value="custom">Custom…</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-actions col-12">
|
||||
<button type="submit" class="btn primary">Generate convert link</button>
|
||||
<button class="btn ghost" type="button" data-action="clear">Clear</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="result-box" data-state="empty" aria-live="polite" aria-atomic="true">
|
||||
<div class="result-row">
|
||||
<input id="generated-link" type="text" value="{{ generated_link or '' }}" readonly
|
||||
placeholder="Link will appear here…">
|
||||
<div class="result-buttons">
|
||||
<button class="btn" type="button" data-action="copy" data-target="#generated-link">Copy</button>
|
||||
<a class="btn outline" id="open-link" href="{{ generated_link or '#' }}" target="_blank"
|
||||
rel="noopener" aria-disabled="{{ 'false' if generated_link else 'true' }}">Open</a>
|
||||
</div>
|
||||
</div>
|
||||
<small class="hint">The preview updates live while you type.</small>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card recent-card">
|
||||
<div class="section-head">
|
||||
<h2>Recent converts</h2>
|
||||
<div class="head-actions">
|
||||
<button class="btn ghost" type="button" data-action="collapse" aria-expanded="false"
|
||||
aria-controls="recent-list">Expand</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="recent-list" class="recent-list" style="display:none">
|
||||
{% if recent_links %}
|
||||
{% for link_data in recent_links %}
|
||||
<article class="link-item">
|
||||
<div class="link-main">
|
||||
<a class="mono ellipsis" title="{{ link_data[1] }}"
|
||||
href="/convert?url={{ link_data[1]|urlencode }}&ip={{ link_data[2] }}" target="_blank"
|
||||
rel="noopener">
|
||||
{{ link_data[1] }}
|
||||
</a>
|
||||
<span class="arrow">→</span>
|
||||
<span class="mono ellipsis" title="{{ link_data[2] }}">{{ link_data[2] }}</span>
|
||||
</div>
|
||||
<div class="link-meta">
|
||||
<span class="timestamp">{{ link_data[0]|datetimeformat }}</span>
|
||||
<div class="link-actions">
|
||||
<button class="btn tiny" type="button" data-action="copy-text"
|
||||
data-text="/convert?url={{ link_data[1]|urlencode }}&ip={{ link_data[2] }}">Copy</button>
|
||||
<a class="btn tiny outline"
|
||||
href="/convert?url={{ link_data[1]|urlencode }}&ip={{ link_data[2] }}" target="_blank"
|
||||
rel="noopener">Open</a>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<p class="muted">Empty..</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer class="site-footer">
|
||||
<div>© 2025 <a href="https://www.linuxiarz.pl" target="_blank" rel="noopener">linuxiarz.pl</a> · All rights
|
||||
reserved</div>
|
||||
<div class="meta">Your IP: <strong>{{ client_ip }}</strong> · UA: <strong>{{ user_agent }}</strong></div>
|
||||
</footer>
|
||||
|
||||
<div id="toast" role="status" aria-live="polite" aria-atomic="true"></div>
|
||||
|
||||
<script defer src="{{ url_for('static', filename='js/main.js') }}"></script>
|
||||
<script>
|
||||
function toggleTheme() {
|
||||
const body = document.body;
|
||||
body.setAttribute('data-theme',
|
||||
body.getAttribute('data-theme') === 'dark' ? 'light' : 'dark');
|
||||
localStorage.setItem('theme', body.getAttribute('data-theme'));
|
||||
}
|
||||
|
||||
function copyToClipboard(btn) {
|
||||
const copyText = document.querySelector("input[readonly]");
|
||||
copyText.select();
|
||||
document.execCommand("copy");
|
||||
|
||||
btn.classList.add('copied');
|
||||
setTimeout(() => btn.classList.remove('copied'), 2000);
|
||||
}
|
||||
|
||||
// Load saved theme
|
||||
const savedTheme = localStorage.getItem('theme') || 'dark';
|
||||
document.body.setAttribute('data-theme', savedTheme);
|
||||
// no-flash theme bootstrap
|
||||
(function () { const t = localStorage.getItem('theme') || 'dark'; document.documentElement.setAttribute('data-theme', t); })();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
</html>
|
@@ -1,70 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Hosts Converter</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; max-width: 800px; margin: 20px auto; padding: 20px; }
|
||||
form { background: #f5f5f5; padding: 20px; border-radius: 5px; }
|
||||
input[type="text"] { width: 100%; padding: 8px; margin: 5px 0; }
|
||||
.result-box { margin: 20px 0; padding: 15px; border: 1px solid #ddd; background: #fff; }
|
||||
.recent-links { margin-top: 30px; border-top: 1px solid #eee; padding-top: 20px; }
|
||||
.link-item { margin: 10px 0; padding: 10px; background: #f8f9fa; border-radius: 3px; }
|
||||
.timestamp { color: #666; font-size: 0.9em; }
|
||||
button { padding: 8px 15px; background: #007bff; color: white; border: none; border-radius: 3px; cursor: pointer; }
|
||||
button:hover { background: #0056b3; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hosts File Converter</h1>
|
||||
|
||||
<form method="GET" action="/">
|
||||
<p>
|
||||
<label>URL to hosts file:<br>
|
||||
<input type="text" name="url" required
|
||||
placeholder="np. paulgb.github.io/BarbBlock/blacklists/hosts-file.txt">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label>Target IP:
|
||||
<input type="text" name="ip" pattern="\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}"
|
||||
value="195.187.6.34" required>
|
||||
</label>
|
||||
</p>
|
||||
<button type="submit">Generate convert link</button>
|
||||
</form>
|
||||
|
||||
{% if generated_link %}
|
||||
<div class="result-box">
|
||||
<h3>Link to MikroTik/Adguard:</h3>
|
||||
<input type="text" value="{{ generated_link }}" readonly
|
||||
style="width: 100%; padding: 8px; margin: 5px 0;">
|
||||
<button onclick="copyToClipboard()">Copy link</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="recent-links">
|
||||
<h3>Last converts:</h3>
|
||||
{% if recent_links %}
|
||||
{% for link_data in recent_links %}
|
||||
<div class="link-item">
|
||||
<div class="timestamp">{{ link_data[0]|datetimeformat }}</div>
|
||||
<a href="/convert?url={{ link_data[1]|urlencode }}&ip={{ link_data[2] }}" target="_blank">
|
||||
{{ link_data[1] }} → {{ link_data[2] }}
|
||||
</a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<p>Empty..</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function copyToClipboard() {
|
||||
const copyText = document.querySelector("input[readonly]");
|
||||
copyText.select();
|
||||
document.execCommand("copy");
|
||||
alert("OK!");
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@@ -1,16 +1,216 @@
|
||||
<!-- templates/stats.html -->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<html lang="en" data-theme="dark">
|
||||
|
||||
<head>
|
||||
<title>Statistics</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Stats</title>
|
||||
<meta name="theme-color" content="#0f1115">
|
||||
<link rel="preload" href="{{ url_for('static', filename='css/main.css') }}" as="style">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/main.css') }}">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Download Statistics</h1>
|
||||
<table>
|
||||
<tr><th>URL</th><th>Hits</th></tr>
|
||||
{% for url, count in stats.items() %}
|
||||
<tr><td>{{ url }}</td><td>{{ count }}</td></tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
<header class="site-header">
|
||||
<div class="brand">
|
||||
<svg aria-hidden="true" width="24" height="24" viewBox="0 0 24 24">
|
||||
<path d="M4 4h16v4H4zM4 10h10v4H4zM4 16h16v4H4z" fill="currentColor" />
|
||||
</svg>
|
||||
<span>Hosts Converter</span>
|
||||
</div>
|
||||
<nav class="actions">
|
||||
<button class="btn ghost" type="button" data-action="toggle-theme" aria-label="Toggle theme">🌓</button>
|
||||
<a class="btn ghost" href="/" rel="nofollow">Home</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main class="container container--wide">
|
||||
<!-- KPIs -->
|
||||
<section class="card kpi-card">
|
||||
<h2 class="section-title">Overview</h2>
|
||||
<div class="kpi-grid">
|
||||
<div class="kpi">
|
||||
<div class="kpi-label">Convert requests</div>
|
||||
<div class="kpi-value">{{ stats.get('stats:convert_requests', 0) }}</div>
|
||||
</div>
|
||||
<div class="kpi">
|
||||
<div class="kpi-label">Successful conversions</div>
|
||||
<div class="kpi-value">{{ stats.get('stats:conversions_success', 0) }}</div>
|
||||
</div>
|
||||
<div class="kpi">
|
||||
<div class="kpi-label">Errors 4xx</div>
|
||||
<div class="kpi-value">{{ stats.get('stats:errors_400', 0) }}</div>
|
||||
</div>
|
||||
<div class="kpi">
|
||||
<div class="kpi-label">Errors 5xx</div>
|
||||
<div class="kpi-value">{{ stats.get('stats:errors_500', 0) }}</div>
|
||||
</div>
|
||||
<div class="kpi">
|
||||
<div class="kpi-label">Avg processing (s)</div>
|
||||
<div class="kpi-value">{{ '%.3f' % detailed.processing_time_avg_sec }}</div>
|
||||
</div>
|
||||
<div class="kpi">
|
||||
<div class="kpi-label">Avg content (bytes)</div>
|
||||
<div class="kpi-value">{{ detailed.content_size_avg_bytes }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Recent converts -->
|
||||
<section class="card">
|
||||
<div class="section-head">
|
||||
<h2>Recent converts (latest {{ recent|length }})</h2>
|
||||
<div class="head-actions">
|
||||
<input class="table-filter" type="search" placeholder="Filter…" data-action="filter-table"
|
||||
data-target="#recent-table">
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-wrap">
|
||||
<table id="recent-table" class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Time</th>
|
||||
<th>URL</th>
|
||||
<th>Target IP</th>
|
||||
<th>Client</th>
|
||||
<th>User agent</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for row in recent %}
|
||||
{% set q = row.get('url','') %}
|
||||
{% set parts = q.split('&ip=') %}
|
||||
{% set url = parts[0].replace('/convert?url=', '') | urlencode %}
|
||||
{% set ip = (parts[1] if parts|length > 1 else '') %}
|
||||
<tr>
|
||||
<td class="mono nowrap">{{ row.time|datetimeformat }}</td>
|
||||
<td class="mono ellipsis" title="{{ url|safe }}">
|
||||
{{ url|safe }}
|
||||
</td>
|
||||
<td class="mono">{{ ip }}</td>
|
||||
<td class="mono ellipsis" title="{{ row.hostname }} ({{ row.ip }})">
|
||||
{{ row.hostname }} ({{ row.ip }})
|
||||
</td>
|
||||
<td class="ellipsis" title="{{ row.user_agent }}">{{ row.user_agent }}</td>
|
||||
<td class="actions">
|
||||
<a class="btn tiny outline" href="{{ q }}" target="_blank" rel="noopener">Open</a>
|
||||
<button class="btn tiny" data-action="copy-text" data-text="{{ q }}">Copy</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Top tables -->
|
||||
<section class="card">
|
||||
<div class="section-head">
|
||||
<h2>Top sources</h2>
|
||||
</div>
|
||||
<div class="grid">
|
||||
<div class="col-6">
|
||||
<h3 class="subhead">Source URLs</h3>
|
||||
<div class="table-wrap">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>URL</th>
|
||||
<th class="right">Hits</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for u, c in url_requests.items()|sort(attribute=1, reverse=True) %}
|
||||
<tr>
|
||||
<td class="mono ellipsis" title="{{ u }}">{{ u }}</td>
|
||||
<td class="right">{{ c }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-6">
|
||||
<h3 class="subhead">Target IPs</h3>
|
||||
<div class="table-wrap">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>IP</th>
|
||||
<th class="right">Hits</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for ip, c in target_ips.items()|sort(attribute=1, reverse=True) %}
|
||||
<tr>
|
||||
<td class="mono">{{ ip }}</td>
|
||||
<td class="right">{{ c }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-6">
|
||||
<h3 class="subhead">User agents</h3>
|
||||
<div class="table-wrap">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>User agent</th>
|
||||
<th class="right">Hits</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for ua, c in user_agents.items()|sort(attribute=1, reverse=True) %}
|
||||
<tr>
|
||||
<td class="ellipsis" title="{{ ua }}">{{ ua }}</td>
|
||||
<td class="right">{{ c }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-6">
|
||||
<h3 class="subhead">Client IPs</h3>
|
||||
<div class="table-wrap">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Client IP</th>
|
||||
<th class="right">Hits</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for ip, c in client_ips.items()|sort(attribute=1, reverse=True) %}
|
||||
<tr>
|
||||
<td class="mono">{{ ip }}</td>
|
||||
<td class="right">{{ c }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
|
||||
<footer class="site-footer">
|
||||
<div>© 2025 <a href="https://www.linuxiarz.pl" target="_blank" rel="noopener">linuxiarz.pl</a></div>
|
||||
<div class="meta">Your IP: <strong>{{ request.remote_addr }}</strong></div>
|
||||
</footer>
|
||||
|
||||
<div id="toast" role="status" aria-live="polite" aria-atomic="true"></div>
|
||||
<script defer src="{{ url_for('static', filename='js/main.js') }}"></script>
|
||||
<script defer src="{{ url_for('static', filename='js/stats.js') }}"></script>
|
||||
<script>(function () { const t = localStorage.getItem('theme') || 'dark'; document.documentElement.setAttribute('data-theme', t); })();</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
</html>
|
Reference in New Issue
Block a user