feat: BIND DNS Web Manager - Web interface for managing BIND9 DNS server
- Flask + SQLite web app with user authentication - Zone management: create/delete zones, edit raw zone files - Record management: add/delete A/AAAA/CNAME/MX/TXT/NS/PTR/SRV/CAA records - Service control: start/stop/restart/reload BIND via systemctl/rndc - Configuration editor: edit named.conf.options/local with syntax validation - DNS query testing: online dig tool - Audit log: all operations logged with user/timestamp - BIND9 backend, listening on port 53
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% block title %}DNS 管理系统{% endblock %}</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
||||
</head>
|
||||
<body>
|
||||
{% if current_user %}
|
||||
<nav class="navbar">
|
||||
<div class="nav-brand">
|
||||
<span class="logo">🌐</span>
|
||||
<a href="{{ url_for('dashboard') }}">DNS 管理系统</a>
|
||||
</div>
|
||||
<div class="nav-links">
|
||||
<a href="{{ url_for('dashboard') }}" class="{% if request.endpoint == 'dashboard' %}active{% endif %}">仪表盘</a>
|
||||
<a href="{{ url_for('zone_list') }}" class="{% if request.endpoint in ('zone_list', 'zone_create', 'zone_detail', 'zone_raw') %}active{% endif %}">域名管理</a>
|
||||
<a href="{{ url_for('query_test') }}" class="{% if request.endpoint == 'query_test' %}active{% endif %}">DNS 查询</a>
|
||||
<a href="{{ url_for('config_view') }}" class="{% if request.endpoint == 'config_view' %}active{% endif %}">配置管理</a>
|
||||
<a href="{{ url_for('logs') }}" class="{% if request.endpoint == 'logs' %}active{% endif %}">操作日志</a>
|
||||
</div>
|
||||
<div class="nav-user">
|
||||
<span>{{ current_user.username }}</span>
|
||||
<a href="{{ url_for('change_password') }}" class="btn-sm">改密</a>
|
||||
<a href="{{ url_for('logout') }}" class="btn-sm btn-danger">退出</a>
|
||||
</div>
|
||||
</nav>
|
||||
{% endif %}
|
||||
|
||||
<main class="container">
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
<div class="flash-messages">
|
||||
{% for category, message in messages %}
|
||||
<div class="flash flash-{{ category }}">{{ message }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
|
||||
<footer class="footer">
|
||||
<p>BIND DNS Web Manager © 2026 | BIND 9.18+ | <a href="https://www.isc.org/bind/" target="_blank">ISC BIND</a></p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,31 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}修改密码 - DNS{% endblock %}
|
||||
{% block content %}
|
||||
<div class="page-header">
|
||||
<h1>修改密码</h1>
|
||||
</div>
|
||||
|
||||
<div class="card" style="max-width:500px;margin:0 auto">
|
||||
<div class="card-body">
|
||||
<form method="POST" class="form">
|
||||
<div class="form-group">
|
||||
<label>旧密码 <span class="required">*</span></label>
|
||||
<input type="password" name="old_password" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>新密码 <span class="required">*</span></label>
|
||||
<input type="password" name="new_password" required minlength="6">
|
||||
<p class="form-hint">至少 6 位</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>确认新密码 <span class="required">*</span></label>
|
||||
<input type="password" name="confirm_password" required>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">修改密码</button>
|
||||
<a href="{{ url_for('dashboard') }}" class="btn btn-secondary">取消</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,90 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}配置管理 - DNS{% endblock %}
|
||||
{% block content %}
|
||||
<div class="page-header">
|
||||
<h1>BIND 配置管理</h1>
|
||||
</div>
|
||||
|
||||
<div class="config-status {% if config_ok %}config-ok{% else %}config-err{% endif %}">
|
||||
<span class="config-icon">{% if config_ok %}✅{% else %}❌{% endif %}</span>
|
||||
<span>配置检查: {% if config_ok %}通过{% else %}有错误{% endif %}</span>
|
||||
{% if not config_ok %}
|
||||
<pre class="config-error">{{ config_msg }}</pre>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h2>named.conf (主配置)</h2>
|
||||
<span class="text-muted text-sm">只读</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<pre class="config-viewer">{{ main_content }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h2>named.conf.options (选项配置)</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="POST" action="{{ url_for('config_options_save') }}">
|
||||
<div class="form-group">
|
||||
<textarea name="content" class="code-editor" rows="20" spellcheck="false">{{ options_content }}</textarea>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">💾 保存并重载</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h2>named.conf.local (本地区域配置)</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="POST" action="{{ url_for('config_local_save') }}">
|
||||
<div class="form-group">
|
||||
<textarea name="content" class="code-editor" rows="20" spellcheck="false">{{ local_content }}</textarea>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">💾 保存并重载</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header"><h2>配置说明</h2></div>
|
||||
<div class="card-body">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr><th>配置文件</th><th>用途</th><th>路径</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>named.conf</strong></td>
|
||||
<td>主配置,包含其他配置文件</td>
|
||||
<td class="text-mono text-sm">/etc/bind/named.conf</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>named.conf.options</strong></td>
|
||||
<td>全局选项: 监听端口/地址、转发器、查询权限等</td>
|
||||
<td class="text-mono text-sm">/etc/bind/named.conf.options</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>named.conf.local</strong></td>
|
||||
<td>区域(zone)声明 - 由 Web UI 自动管理</td>
|
||||
<td class="text-mono text-sm">/etc/bind/named.conf.local</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>zone files</strong></td>
|
||||
<td>DNS 记录文件</td>
|
||||
<td class="text-mono text-sm">/etc/bind/zones/db.*</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,125 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}仪表盘 - DNS 管理{% endblock %}
|
||||
{% block content %}
|
||||
<div class="dashboard">
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon {% if status.active %}stat-ok{% else %}stat-err{% endif %}">●</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-label">BIND 服务</div>
|
||||
<div class="stat-value {% if status.active %}text-ok{% else %}text-err{% endif %}">
|
||||
{{ status.status | upper }}
|
||||
</div>
|
||||
<div class="stat-sub">开机自启: {{ '是' if status.enabled else '否' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon">🌍</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-label">域名数量</div>
|
||||
<div class="stat-value">{{ zone_count }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon">📋</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-label">记录总数</div>
|
||||
<div class="stat-value">{{ total_records }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon">⚙️</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-label">BIND 版本</div>
|
||||
<div class="stat-value-small">{{ version[:40] }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dashboard-grid">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h2>服务控制</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="service-controls">
|
||||
<a href="{{ url_for('service_control', action='start') }}" class="btn btn-success" onclick="return confirm('启动 BIND 服务?')">▶ 启动</a>
|
||||
<a href="{{ url_for('service_control', action='stop') }}" class="btn btn-warning" onclick="return confirm('停止 BIND 服务?')">⏹ 停止</a>
|
||||
<a href="{{ url_for('service_control', action='restart') }}" class="btn btn-info" onclick="return confirm('重启 BIND 服务?')">🔄 重启</a>
|
||||
<a href="{{ url_for('service_control', action='reload') }}" class="btn btn-primary" onclick="return confirm('重载 BIND 配置?')">🔁 重载配置</a>
|
||||
</div>
|
||||
<div class="service-note">
|
||||
<p>📝 <strong>提示:</strong> BIND 监听标准 53 端口,可直接用 <code>dig a.wxj.aa</code> 查询。</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h2>域名概览</h2>
|
||||
<a href="{{ url_for('zone_create') }}" class="btn btn-primary btn-sm">+ 添加域名</a>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if zones %}
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>域名</th>
|
||||
<th>类型</th>
|
||||
<th>记录数</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for z in zones %}
|
||||
<tr>
|
||||
<td><a href="{{ url_for('zone_detail', zone_name=z.name) }}"><strong>{{ z.name }}</strong></a></td>
|
||||
<td><span class="badge">{{ z.type }}</span></td>
|
||||
<td>{{ z.record_count }}</td>
|
||||
<td>
|
||||
<a href="{{ url_for('zone_detail', zone_name=z.name) }}" class="btn-sm btn-primary">管理</a>
|
||||
<a href="{{ url_for('zone_raw', zone_name=z.name) }}" class="btn-sm btn-info">原始文件</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<div class="empty-state">
|
||||
<p>还没有任何域名</p>
|
||||
<a href="{{ url_for('zone_create') }}" class="btn btn-primary">+ 创建第一个域名</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h2>最近操作</h2>
|
||||
<a href="{{ url_for('logs') }}" class="btn-sm btn-info">查看全部</a>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if recent_logs %}
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr><th>时间</th><th>用户</th><th>操作</th><th>详情</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for log in recent_logs %}
|
||||
<tr>
|
||||
<td class="text-mono">{{ log.timestamp.strftime('%Y-%m-%d %H:%M:%S') }}</td>
|
||||
<td>{{ log.user }}</td>
|
||||
<td>{{ log.action }}</td>
|
||||
<td class="text-mono text-sm">{{ log.detail }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p class="text-muted">暂无操作记录</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,9 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}错误 {{ code }}{% endblock %}
|
||||
{% block content %}
|
||||
<div class="error-page">
|
||||
<div class="error-code">{{ code }}</div>
|
||||
<div class="error-msg">{{ message }}</div>
|
||||
<a href="{{ url_for('dashboard') }}" class="btn btn-primary">返回首页</a>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,27 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}登录 - DNS 管理{% endblock %}
|
||||
{% block content %}
|
||||
<div class="login-wrap">
|
||||
<div class="login-card">
|
||||
<div class="login-header">
|
||||
<h1>🌐 DNS 管理系统</h1>
|
||||
<p>BIND9 Web 管理界面</p>
|
||||
</div>
|
||||
<form method="POST" class="login-form">
|
||||
<div class="form-group">
|
||||
<label>用户名</label>
|
||||
<input type="text" name="username" placeholder="admin" autofocus required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>密码</label>
|
||||
<input type="password" name="password" placeholder="admin" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-block">登 录</button>
|
||||
</form>
|
||||
<div class="login-hint">
|
||||
<p>默认账号: <code>admin</code> / <code>admin</code></p>
|
||||
<p>首次登录后请修改密码</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,48 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}操作日志 - DNS{% endblock %}
|
||||
{% block content %}
|
||||
<div class="page-header">
|
||||
<h1>操作日志</h1>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
{% if logs %}
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>时间</th>
|
||||
<th>用户</th>
|
||||
<th>操作</th>
|
||||
<th>详情</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for log in logs %}
|
||||
<tr>
|
||||
<td class="text-muted">{{ log.id }}</td>
|
||||
<td class="text-mono text-sm">{{ log.timestamp.strftime('%Y-%m-%d %H:%M:%S') }}</td>
|
||||
<td>{{ log.user }}</td>
|
||||
<td>{{ log.action }}</td>
|
||||
<td class="text-mono text-sm">{{ log.detail }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="pagination">
|
||||
{% if pagination.has_prev %}
|
||||
<a href="{{ url_for('logs', page=pagination.prev_num) }}" class="btn-sm btn-secondary">← 上一页</a>
|
||||
{% endif %}
|
||||
<span class="page-info">第 {{ pagination.page }} / {{ pagination.pages }} 页 (共 {{ pagination.total }} 条)</span>
|
||||
{% if pagination.has_next %}
|
||||
<a href="{{ url_for('logs', page=pagination.next_num) }}" class="btn-sm btn-secondary">下一页 →</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="empty-state"><p>暂无操作记录</p></div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,57 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}DNS 查询测试 - DNS{% endblock %}
|
||||
{% block content %}
|
||||
<div class="page-header">
|
||||
<h1>DNS 查询测试</h1>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<form method="POST" class="form">
|
||||
<div class="form-row">
|
||||
<div class="form-group" style="flex:1">
|
||||
<label>域名</label>
|
||||
<input type="text" name="domain" placeholder="example.com" required
|
||||
value="{{ result.domain if result else '' }}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>记录类型</label>
|
||||
<select name="qtype" style="width:120px">
|
||||
{% for t in qtypes %}
|
||||
<option value="{{ t }}" {% if result and result.qtype == t %}selected{% endif %}>{{ t }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>DNS 服务器</label>
|
||||
<input type="text" name="server" placeholder="留空=本地:53"
|
||||
value="{{ result.server if result else '' }}" style="width:200px">
|
||||
</div>
|
||||
<div class="form-group form-actions-vertical">
|
||||
<button type="submit" class="btn btn-primary">🔍 查询</button>
|
||||
</div>
|
||||
</div>
|
||||
<p class="form-hint">留空 DNS 服务器默认查询本地 BIND (127.0.0.1:53)</p>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if result %}
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h2>查询结果</h2>
|
||||
<span class="badge {% if result.success %}badge-ok{% else %}badge-err{% endif %}">
|
||||
{% if result.success %}成功{% else %}失败{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="query-meta">
|
||||
<span><strong>域名:</strong> {{ result.domain }}</span>
|
||||
<span><strong>类型:</strong> {{ result.qtype }}</span>
|
||||
<span><strong>服务器:</strong> {{ result.server }}</span>
|
||||
</div>
|
||||
<pre class="query-output">{{ result.output }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,123 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{{ zone.name }} - DNS{% endblock %}
|
||||
{% block content %}
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<h1>{{ zone.name }}</h1>
|
||||
<div class="zone-meta">
|
||||
<span class="badge badge-{{ zone.type }}">{{ zone.type }}</span>
|
||||
<span class="text-mono text-sm">{{ zone.filename }}</span>
|
||||
<span class="text-muted text-sm">共 {{ records|length }} 条记录</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<a href="{{ url_for('zone_raw', zone_name=zone.name) }}" class="btn btn-info btn-sm">📝 原始文件</a>
|
||||
<form method="POST" action="{{ url_for('zone_delete', zone_name=zone.name) }}" style="display:inline" onsubmit="return confirm('确定删除域名 {{ zone.name }} 及所有记录?')">
|
||||
<button type="submit" class="btn btn-danger btn-sm">🗑 删除域名</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if soa %}
|
||||
<div class="card">
|
||||
<div class="card-header"><h2>SOA 记录</h2></div>
|
||||
<div class="card-body">
|
||||
<table class="table table-compact">
|
||||
<tr>
|
||||
<td class="text-muted">主 NS</td><td class="text-mono">{{ soa.raw.split()[0] if soa.raw else '-' }}</td>
|
||||
<td class="text-muted">管理员</td><td class="text-mono">{{ soa.raw.split()[1] if soa.raw and soa.raw.split()|length > 1 else '-' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-muted">Serial</td><td class="text-mono">{{ soa.raw.split()[2] if soa.raw and soa.raw.split()|length > 2 else '-' }}</td>
|
||||
<td class="text-muted">Refresh</td><td class="text-mono">{{ soa.raw.split()[3] if soa.raw and soa.raw.split()|length > 3 else '-' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-muted">Retry</td><td class="text-mono">{{ soa.raw.split()[4] if soa.raw and soa.raw.split()|length > 4 else '-' }}</td>
|
||||
<td class="text-muted">Expire</td><td class="text-mono">{{ soa.raw.split()[5] if soa.raw and soa.raw.split()|length > 5 else '-' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-muted">Min TTL</td><td class="text-mono">{{ soa.raw.split()[6] if soa.raw and soa.raw.split()|length > 6 else '-' }}</td>
|
||||
<td colspan="2"></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h2>DNS 记录</h2>
|
||||
<button type="button" class="btn btn-primary btn-sm" onclick="document.getElementById('addRecordForm').style.display='block'">+ 添加记录</button>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="addRecordForm" style="display:none" class="record-form-wrap">
|
||||
<form method="POST" action="{{ url_for('record_add', zone_name=zone.name) }}" class="record-form">
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label>名称</label>
|
||||
<input type="text" name="name" placeholder="@ 或 www" style="width:150px">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>TTL</label>
|
||||
<input type="text" name="ttl" placeholder="留空=默认" style="width:100px">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>类型</label>
|
||||
<select name="type" id="recordType" style="width:100px">
|
||||
{% for t in record_types %}
|
||||
<option value="{{ t }}">{{ t }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group" style="flex:1">
|
||||
<label>数据</label>
|
||||
<input type="text" name="data" placeholder="如 192.168.1.1 或 target.example.com." style="width:100%">
|
||||
</div>
|
||||
<div class="form-group form-actions-vertical">
|
||||
<button type="submit" class="btn btn-primary btn-sm">添加</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-hints">
|
||||
<p>A: IPv4地址 | AAAA: IPv6地址 | CNAME: 别名(以.结尾) | MX: 优先级 目标(以.结尾)</p>
|
||||
<p>TXT: 文本 | NS: NS服务器(以.结尾) | PTR: 反向解析(以.结尾) | SRV: 优先级 权重 端口 目标</p>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{% if records %}
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>名称</th>
|
||||
<th>TTL</th>
|
||||
<th>类型</th>
|
||||
<th>数据</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for r in records %}
|
||||
<tr>
|
||||
<td class="text-muted">{{ loop.index0 }}</td>
|
||||
<td class="text-mono">{{ r.name }}</td>
|
||||
<td class="text-mono text-muted">{{ r.ttl }}</td>
|
||||
<td><span class="badge badge-{{ r.type }}">{{ r.type }}</span></td>
|
||||
<td class="text-mono">{{ r.data }}</td>
|
||||
<td>
|
||||
<form method="POST" action="{{ url_for('record_delete', zone_name=zone.name, idx=loop.index0) }}" style="display:inline" onsubmit="return confirm('确定删除 {{ r.name }} {{ r.type }} {{ r.data }}?')">
|
||||
<button type="submit" class="btn-sm btn-danger">删除</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<div class="empty-state">
|
||||
<p>此域名还没有记录</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,45 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{% if zone %}编辑域名{% else %}创建域名{% endif %} - DNS{% endblock %}
|
||||
{% block content %}
|
||||
<div class="page-header">
|
||||
<h1>{% if zone %}编辑域名{% else %}创建域名{% endif %}</h1>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<form method="POST" class="form">
|
||||
<div class="form-group">
|
||||
<label>域名 <span class="required">*</span></label>
|
||||
<input type="text" name="zone_name" placeholder="example.com" required
|
||||
value="{{ zone.name if zone else '' }}">
|
||||
<p class="form-hint">不含末尾的点,例如 example.com</p>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>类型</label>
|
||||
<select name="zone_type">
|
||||
<option value="master" selected>master (主域)</option>
|
||||
<option value="slave">slave (从域)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>主 NS 服务器</label>
|
||||
<input type="text" name="ns_server" placeholder="ns1.example.com">
|
||||
<p class="form-hint">留空则自动使用 ns1.<域名></p>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>管理员邮箱</label>
|
||||
<input type="text" name="admin_email" placeholder="admin@example.com">
|
||||
<p class="form-hint">留空则自动使用 admin.<域名>,会自动将 @ 转为 .</p>
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">创建域名</button>
|
||||
<a href="{{ url_for('zone_list') }}" class="btn btn-secondary">取消</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,45 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{{ zone.name }} 原始文件 - DNS{% endblock %}
|
||||
{% block content %}
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<h1>原始 Zone 文件: {{ zone.name }}</h1>
|
||||
<p class="text-muted text-sm">文件: {{ zone.file }}</p>
|
||||
</div>
|
||||
<a href="{{ url_for('zone_detail', zone_name=zone.name) }}" class="btn btn-secondary">← 返回</a>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<form method="POST">
|
||||
<div class="form-group">
|
||||
<textarea name="content" class="code-editor" rows="25" spellcheck="false">{{ content }}</textarea>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">💾 保存并重载</button>
|
||||
<a href="{{ url_for('zone_detail', zone_name=zone.name) }}" class="btn btn-secondary">取消</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header"><h2>格式说明</h2></div>
|
||||
<div class="card-body">
|
||||
<pre class="text-sm text-muted">
|
||||
$TTL 86400 ; 默认 TTL
|
||||
@ IN SOA ns1.example.com. admin.example.com. (
|
||||
2026070901 ; Serial - 每次修改需递增
|
||||
3600 ; Refresh - 从服务器刷新间隔
|
||||
900 ; Retry - 刷新失败重试间隔
|
||||
604800 ; Expire - 过期时间
|
||||
86400 ; Minimum - 否定缓存TTL
|
||||
)
|
||||
@ IN NS ns1.example.com. ; NS 记录
|
||||
www IN A 192.168.1.100 ; A 记录
|
||||
mail IN A 192.168.1.200 ; A 记录
|
||||
@ IN MX 10 mail.example.com. ; MX 记录
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,48 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}域名管理 - DNS{% endblock %}
|
||||
{% block content %}
|
||||
<div class="page-header">
|
||||
<h1>域名管理</h1>
|
||||
<a href="{{ url_for('zone_create') }}" class="btn btn-primary">+ 添加域名</a>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
{% if zones %}
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>域名</th>
|
||||
<th>类型</th>
|
||||
<th>Zone 文件</th>
|
||||
<th>记录数</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for z in zones %}
|
||||
<tr>
|
||||
<td><a href="{{ url_for('zone_detail', zone_name=z.name) }}"><strong>{{ z.name }}</strong></a></td>
|
||||
<td><span class="badge badge-{{ z.type }}">{{ z.type }}</span></td>
|
||||
<td class="text-mono text-sm">{{ z.filename }}</td>
|
||||
<td>{{ z.record_count }}</td>
|
||||
<td>
|
||||
<a href="{{ url_for('zone_detail', zone_name=z.name) }}" class="btn-sm btn-primary">管理记录</a>
|
||||
<a href="{{ url_for('zone_raw', zone_name=z.name) }}" class="btn-sm btn-info">原始文件</a>
|
||||
<form method="POST" action="{{ url_for('zone_delete', zone_name=z.name) }}" style="display:inline" onsubmit="return confirm('确定删除域名 {{ z.name }} 及所有记录?')">
|
||||
<button type="submit" class="btn-sm btn-danger">删除</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<div class="empty-state">
|
||||
<p>还没有任何域名</p>
|
||||
<a href="{{ url_for('zone_create') }}" class="btn btn-primary">+ 创建第一个域名</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user