Initial commit: DHCP Web Manager
Flask-based web UI for managing ISC DHCP server (dhcpd). Features: - subnet / static host binding CRUD via web - listen interface configuration (INTERFACESv4) - active lease query + pool utilization stats - service status, reload/restart, syntax check - user auth + operation logs - automatic backup before each config change - safe change pipeline: write file → dhcpd -t → systemctl restart + is-active verify - error messages include journalctl tail for diagnosis
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
<!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 %}DHCP 管理{% endblock %}</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
||||
</head>
|
||||
<body>
|
||||
{% if current_user %}
|
||||
<header class="topbar">
|
||||
<div class="topbar-inner">
|
||||
<a class="brand" href="{{ url_for('dashboard') }}">📡 DHCP 管理</a>
|
||||
<nav class="nav">
|
||||
<a href="{{ url_for('dashboard') }}" class="{% if request.endpoint=='dashboard' %}active{% endif %}">总览</a>
|
||||
<a href="{{ url_for('subnet_list') }}" class="{% if request.endpoint in ['subnet_list','subnet_new','subnet_edit'] %}active{% endif %}">子网</a>
|
||||
<a href="{{ url_for('host_list') }}" class="{% if request.endpoint in ['host_list','host_new','host_edit'] %}active{% endif %}">静态绑定</a>
|
||||
<a href="{{ url_for('lease_list') }}" class="{% if request.endpoint=='lease_list' %}active{% endif %}">租约</a>
|
||||
<a href="{{ url_for('config_view') }}" class="{% if request.endpoint=='config_view' %}active{% endif %}">服务配置</a>
|
||||
{% if is_admin %}
|
||||
<a href="{{ url_for('log_list') }}" class="{% if request.endpoint=='log_list' %}active{% endif %}">日志</a>
|
||||
{% endif %}
|
||||
</nav>
|
||||
<div class="userbox">
|
||||
<span class="username">{{ current_user.username }}{% if is_admin %} <span class="badge">管理员</span>{% endif %}</span>
|
||||
<a href="{{ url_for('profile') }}" class="link-muted">改密</a>
|
||||
<a href="{{ url_for('logout') }}" class="link-muted">登出</a>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
{% endif %}
|
||||
|
||||
<main class="container">
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
<div class="flashes">
|
||||
{% for cat, msg in messages %}
|
||||
<div class="flash flash-{{ cat }}">{{ msg }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
|
||||
<footer class="footer">
|
||||
<span>DHCP Web Manager · /etc/dhcp/dhcpd.conf</span>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,63 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}服务配置{% endblock %}
|
||||
{% block content %}
|
||||
<div class="page-head">
|
||||
<h1>服务配置</h1>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>服务状态</h2>
|
||||
<p>
|
||||
状态:
|
||||
{% if svc.active %}
|
||||
<span class="badge badge-ok">● 运行中</span>
|
||||
{% else %}
|
||||
<span class="badge badge-warn">○ 未运行</span>
|
||||
{% endif %}
|
||||
</p>
|
||||
{% if svc.status_text %}
|
||||
<pre class="code-block small">{{ svc.status_text }}</pre>
|
||||
{% endif %}
|
||||
<form method="post" style="display:inline">
|
||||
<input type="hidden" name="action" value="reload">
|
||||
<button type="submit" class="btn">reload(不丢连接)</button>
|
||||
</form>
|
||||
<form method="post" style="display:inline"
|
||||
onsubmit="return confirm('重启 dhcpd 会中断当前所有租约续约,确定?');">
|
||||
<input type="hidden" name="action" value="restart">
|
||||
<button type="submit" class="btn btn-warn">restart</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>语法检查</h2>
|
||||
{% if syntax_ok %}
|
||||
<p class="ok-text">✓ 配置语法 OK</p>
|
||||
{% else %}
|
||||
<p class="error-text">✗ 配置语法错误</p>
|
||||
{% endif %}
|
||||
{% if syntax_msg %}
|
||||
<pre class="code-block small">{{ syntax_msg }}</pre>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>监听接口</h2>
|
||||
<p class="muted small">空格分隔多个接口,留空表示监听所有(生产环境强烈建议指定具体接口)</p>
|
||||
<form method="post">
|
||||
<input type="hidden" name="action" value="save_interfaces">
|
||||
<label>INTERFACESv4
|
||||
<input type="text" name="interfaces" value="{{ ifaces|join(' ') }}"
|
||||
placeholder="例如: enp0s31f6 或留空">
|
||||
</label>
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">保存(将重启 dhcpd)</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>当前 dhcpd.conf</h2>
|
||||
<pre class="code-block">{{ conf }}</pre>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,85 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}总览 · DHCP 管理{% endblock %}
|
||||
{% block content %}
|
||||
<div class="stat-grid">
|
||||
<div class="stat-card">
|
||||
<div class="stat-num">{{ subnets|length }}</div>
|
||||
<div class="stat-label">子网数</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-num">{{ hosts|length }}</div>
|
||||
<div class="stat-label">静态绑定</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-num">{{ lease_total }}</div>
|
||||
<div class="stat-label">活跃租约</div>
|
||||
</div>
|
||||
<div class="stat-card {% if svc.active %}stat-ok{% else %}stat-warn{% endif %}">
|
||||
<div class="stat-num">{% if svc.active %}● 运行{% else %}○ 停止{% endif %}</div>
|
||||
<div class="stat-label">dhcpd 状态</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>接口绑定</h2>
|
||||
{% if svc.interfaces %}
|
||||
<p>当前监听: <code>{{ svc.interfaces|join(', ') }}</code></p>
|
||||
{% else %}
|
||||
<p class="warn-text">⚠ 当前未配置监听接口(dhcpd 将不会响应任何请求)</p>
|
||||
{% endif %}
|
||||
<a href="{{ url_for('config_view') }}" class="btn">前往配置</a>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>地址池使用情况</h2>
|
||||
{% if pool_stats %}
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr><th>子网</th><th>地址池范围</th><th>总数</th><th>已用</th><th>静态</th><th>可用</th><th>利用率</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for p in pool_stats %}
|
||||
<tr>
|
||||
<td>{{ p.subnet }}</td>
|
||||
<td><code>{{ p.range }}</code></td>
|
||||
<td>{{ p.total }}</td>
|
||||
<td>{{ p.used }}</td>
|
||||
<td>{{ p.static }}</td>
|
||||
<td>{{ p.available }}</td>
|
||||
<td>
|
||||
<div class="bar"><div class="bar-fill" style="width: {{ p.util_pct }}%"></div></div>
|
||||
<span class="small">{{ p.util_pct }}%</span>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p class="muted">尚未配置任何子网。 <a href="{{ url_for('subnet_new') }}">新增子网</a></p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>最近活跃租约</h2>
|
||||
{% if leases %}
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr><th>IP</th><th>MAC</th><th>主机名</th><th>剩余</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for l in leases %}
|
||||
<tr>
|
||||
<td>{{ l.ip }}</td>
|
||||
<td><code>{{ l.mac or '—' }}</code></td>
|
||||
<td>{{ l.client_hostname or '—' }}</td>
|
||||
<td>{{ l.remaining_seconds()|format_duration }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<p><a href="{{ url_for('lease_list') }}">查看全部租约 →</a></p>
|
||||
{% else %}
|
||||
<p class="muted">暂无活跃租约</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,9 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{{ code }} 错误{% endblock %}
|
||||
{% block content %}
|
||||
<div class="card error-page">
|
||||
<h1>{{ code }}</h1>
|
||||
<p>{{ message }}</p>
|
||||
<a href="{{ url_for('dashboard') }}" class="btn">返回首页</a>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,33 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{{ host.name|default('新增') }} · 静态绑定{% endblock %}
|
||||
{% block content %}
|
||||
<div class="page-head">
|
||||
<h1>{% if host.name %}编辑 host {{ host.name }}{% else %}新增静态绑定{% endif %}</h1>
|
||||
<a href="{{ url_for('host_list') }}" class="btn btn-muted">← 返回</a>
|
||||
</div>
|
||||
|
||||
<form method="post" class="card form">
|
||||
<div class="grid-2">
|
||||
<label>主机名
|
||||
<input type="text" name="name" value="{{ form.name or host.name }}"
|
||||
placeholder="printer-hall" required
|
||||
pattern="[A-Za-z0-9_\-\.]{1,63}">
|
||||
</label>
|
||||
<label>MAC 地址
|
||||
<input type="text" name="mac" value="{{ form.mac or host.mac }}"
|
||||
placeholder="aa:bb:cc:dd:ee:ff" required
|
||||
pattern="([0-9a-fA-F]{2}[:\-]){5}[0-9a-fA-F]{2}">
|
||||
</label>
|
||||
<label>固定 IP
|
||||
<input type="text" name="ip" value="{{ form.ip or host.ip }}"
|
||||
placeholder="192.168.1.50" required
|
||||
pattern="\d{1,3}(\.\d{1,3}){3}">
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">保存并应用</button>
|
||||
<a href="{{ url_for('host_list') }}" class="btn btn-muted">取消</a>
|
||||
</div>
|
||||
<p class="muted small">保存后会自动 reload dhcpd。客户端重启网络或重新获取 IP 后生效。</p>
|
||||
</form>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,44 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}静态绑定{% endblock %}
|
||||
{% block content %}
|
||||
<div class="page-head">
|
||||
<h1>静态绑定(host 声明)</h1>
|
||||
{% if is_admin %}<a href="{{ url_for('host_new') }}" class="btn btn-primary">+ 新增绑定</a>{% endif %}
|
||||
</div>
|
||||
|
||||
{% if hosts %}
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>主机名</th>
|
||||
<th>MAC</th>
|
||||
<th>固定 IP</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for h in hosts %}
|
||||
<tr>
|
||||
<td><strong>{{ h.name }}</strong></td>
|
||||
<td><code>{{ h.mac }}</code></td>
|
||||
<td><code>{{ h.ip }}</code></td>
|
||||
<td class="actions">
|
||||
{% if is_admin %}
|
||||
<a href="{{ url_for('host_edit', name=h.name) }}" class="btn btn-sm">编辑</a>
|
||||
<form method="post" action="{{ url_for('host_delete', name=h.name) }}"
|
||||
onsubmit="return confirm('确认删除 host {{ h.name }}?');" style="display:inline">
|
||||
<button type="submit" class="btn btn-sm btn-danger">删除</button>
|
||||
</form>
|
||||
{% else %}<span class="muted">—</span>{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<div class="card empty">
|
||||
<p>暂无静态绑定</p>
|
||||
{% if is_admin %}<a href="{{ url_for('host_new') }}" class="btn btn-primary">+ 新增第一个</a>{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,59 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}租约{% endblock %}
|
||||
{% block content %}
|
||||
<div class="page-head">
|
||||
<h1>租约查询</h1>
|
||||
<form method="get" class="search-box">
|
||||
<input type="text" name="q" value="{{ q }}" placeholder="按 IP / MAC / 主机名搜索…">
|
||||
<button type="submit" class="btn">搜索</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{% if leases %}
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>IP</th>
|
||||
<th>MAC</th>
|
||||
<th>主机名</th>
|
||||
<th>状态</th>
|
||||
<th>开始</th>
|
||||
<th>结束</th>
|
||||
<th>剩余</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for l in leases %}
|
||||
<tr>
|
||||
<td><code>{{ l.ip }}</code></td>
|
||||
<td><code>{{ l.mac or '—' }}</code></td>
|
||||
<td>{{ l.client_hostname or '—' }}</td>
|
||||
<td>
|
||||
{% if l.binding_state == 'active' %}
|
||||
<span class="badge badge-ok">active</span>
|
||||
{% elif l.binding_state == 'free' %}
|
||||
<span class="badge">free</span>
|
||||
{% else %}
|
||||
<span class="badge badge-warn">{{ l.binding_state }}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td><span class="small">{{ l.starts or '—' }}</span></td>
|
||||
<td><span class="small">{{ l.ends or '—' }}</span></td>
|
||||
<td>
|
||||
{% set rem = l.remaining_seconds() %}
|
||||
{% if rem is none %}—
|
||||
{% elif rem < 0 %}已过期
|
||||
{% else %}{{ rem|format_duration }}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<p class="muted small">共 {{ leases|length }} 条租约</p>
|
||||
{% else %}
|
||||
<div class="card empty">
|
||||
<p>暂无租约记录{% if q %}(搜索 "<code>{{ q }}</code>" 无结果){% endif %}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,14 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}登录 · DHCP 管理{% endblock %}
|
||||
{% block content %}
|
||||
<div class="login-card">
|
||||
<h1>📡 DHCP 管理</h1>
|
||||
<p class="muted">请登录以管理 dhcpd 配置</p>
|
||||
<form method="post" class="login-form">
|
||||
<label>用户名 <input type="text" name="username" required autofocus></label>
|
||||
<label>密码 <input type="password" name="password" required></label>
|
||||
<button type="submit" class="btn btn-primary btn-block">登录</button>
|
||||
</form>
|
||||
<p class="muted small">默认账号:admin / admin(首次登录后请到"改密"修改)</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,47 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}操作日志{% endblock %}
|
||||
{% block content %}
|
||||
<div class="page-head">
|
||||
<h1>操作日志</h1>
|
||||
<span class="muted">共 {{ total }} 条</span>
|
||||
</div>
|
||||
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>时间</th>
|
||||
<th>用户</th>
|
||||
<th>操作</th>
|
||||
<th>对象</th>
|
||||
<th>结果</th>
|
||||
<th>消息</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for l in logs %}
|
||||
<tr>
|
||||
<td><span class="small">{{ l.timestamp.strftime('%Y-%m-%d %H:%M:%S') }}</span></td>
|
||||
<td>{{ l.user }}</td>
|
||||
<td><code>{{ l.action }}</code></td>
|
||||
<td><code>{{ l.target }}</code></td>
|
||||
<td>
|
||||
{% if l.result == 'ok' %}
|
||||
<span class="badge badge-ok">{{ l.result }}</span>
|
||||
{% else %}
|
||||
<span class="badge badge-warn">{{ l.result }}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td><span class="small">{{ l.message }}</span></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{% if pages > 1 %}
|
||||
<div class="pager">
|
||||
{% if page > 1 %}<a href="?page={{ page-1 }}" class="btn">← 上一页</a>{% endif %}
|
||||
<span class="muted">第 {{ page }} / {{ pages }} 页</span>
|
||||
{% if page < pages %}<a href="?page={{ page+1 }}" class="btn">下一页 →</a>{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,16 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}个人资料{% endblock %}
|
||||
{% block content %}
|
||||
<div class="page-head">
|
||||
<h1>修改密码</h1>
|
||||
</div>
|
||||
<form method="post" class="card form" style="max-width:420px">
|
||||
<label>用户名 <input type="text" value="{{ user.username }}" disabled></label>
|
||||
<label>当前密码 <input type="password" name="old_password" required></label>
|
||||
<label>新密码 <input type="password" name="new_password" required minlength="6"></label>
|
||||
<label>再次输入新密码 <input type="password" name="confirm" required minlength="6></label>
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">修改</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,60 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{{ subnet.name|default('新增') }} · 子网{% endblock %}
|
||||
{% block content %}
|
||||
<div class="page-head">
|
||||
<h1>{% if subnet.network %}编辑子网 {{ subnet.network }}/{{ subnet.netmask }}{% else %}新增子网{% endif %}</h1>
|
||||
<a href="{{ url_for('subnet_list') }}" class="btn btn-muted">← 返回</a>
|
||||
</div>
|
||||
|
||||
<form method="post" class="card form">
|
||||
<div class="grid-2">
|
||||
<label>网络号
|
||||
<input type="text" name="network" value="{{ form.network or subnet.network }}"
|
||||
placeholder="192.168.1.0" required pattern="\d{1,3}(\.\d{1,3}){3}">
|
||||
</label>
|
||||
<label>子网掩码
|
||||
<input type="text" name="netmask" value="{{ form.netmask or subnet.netmask }}"
|
||||
placeholder="255.255.255.0" required pattern="(\d{1,3}(\.\d{1,3}){3})|(\d{1,2})">
|
||||
<small class="muted">支持 255.255.255.0 或 /24 写法</small>
|
||||
</label>
|
||||
<label>默认网关
|
||||
<input type="text" name="routers" value="{{ form.routers or subnet.routers }}"
|
||||
placeholder="192.168.1.1" required>
|
||||
</label>
|
||||
<label>DNS 服务器
|
||||
<input type="text" name="dns" value="{{ form.dns or subnet.dns }}"
|
||||
placeholder="8.8.8.8, 8.8.4.4" required>
|
||||
<small class="muted">多个用逗号分隔</small>
|
||||
</label>
|
||||
<label>地址池起始
|
||||
<input type="text" name="range_start" value="{{ form.range_start or subnet.range_start }}"
|
||||
placeholder="192.168.1.100" required>
|
||||
</label>
|
||||
<label>地址池结束
|
||||
<input type="text" name="range_end" value="{{ form.range_end or subnet.range_end }}"
|
||||
placeholder="192.168.1.200" required>
|
||||
</label>
|
||||
<label>默认租期(秒)
|
||||
<input type="number" name="default_lease" min="60" value="{{ form.default_lease or subnet.default_lease or 600 }}" required>
|
||||
</label>
|
||||
<label>最大租期(秒)
|
||||
<input type="number" name="max_lease" min="60" value="{{ form.max_lease or subnet.max_lease or 7200 }}" required>
|
||||
</label>
|
||||
<label>域名(可选)
|
||||
<input type="text" name="domain_name" value="{{ form.domain_name or subnet.domain_name }}"
|
||||
placeholder="lan.local">
|
||||
</label>
|
||||
<label class="checkbox-row">
|
||||
<input type="checkbox" name="authoritative"
|
||||
{% if form.authoritative is defined and form.authoritative %}checked{% endif %}
|
||||
{% if not form and subnet.authoritative %}checked{% endif %}>
|
||||
authoritative(本网络唯一 DHCP 服务器)
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">保存并应用</button>
|
||||
<a href="{{ url_for('subnet_list') }}" class="btn btn-muted">取消</a>
|
||||
</div>
|
||||
<p class="muted small">保存将自动校验语法并 reload 服务。</p>
|
||||
</form>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,54 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}子网配置{% endblock %}
|
||||
{% block content %}
|
||||
<div class="page-head">
|
||||
<h1>子网配置</h1>
|
||||
{% if is_admin %}<a href="{{ url_for('subnet_new') }}" class="btn btn-primary">+ 新增子网</a>{% endif %}
|
||||
</div>
|
||||
|
||||
{% if subnets %}
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>网段</th>
|
||||
<th>掩码</th>
|
||||
<th>网关</th>
|
||||
<th>DNS</th>
|
||||
<th>地址池</th>
|
||||
<th>租期(默认/最大)</th>
|
||||
<th>authoritative</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for s in subnets %}
|
||||
<tr>
|
||||
<td><code>{{ s.network }}</code></td>
|
||||
<td><code>{{ s.netmask }}</code></td>
|
||||
<td><code>{{ s.routers }}</code></td>
|
||||
<td><code>{{ s.dns }}</code></td>
|
||||
<td><code>{{ s.range_start }}<br>~ {{ s.range_end }}</code></td>
|
||||
<td>{{ s.default_lease }}s / {{ s.max_lease }}s</td>
|
||||
<td>{% if s.authoritative %}<span class="badge badge-ok">yes</span>{% else %}<span class="muted">no</span>{% endif %}</td>
|
||||
<td class="actions">
|
||||
{% if is_admin %}
|
||||
<a href="{{ url_for('subnet_edit', network=s.network, netmask=s.netmask) }}" class="btn btn-sm">编辑</a>
|
||||
<form method="post" action="{{ url_for('subnet_delete', network=s.network, netmask=s.netmask) }}"
|
||||
onsubmit="return confirm('确认删除子网 {{ s.network }}/{{ s.netmask }}?');" style="display:inline">
|
||||
<button type="submit" class="btn btn-sm btn-danger">删除</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<span class="muted">—</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<div class="card empty">
|
||||
<p>暂无子网配置</p>
|
||||
{% if is_admin %}<a href="{{ url_for('subnet_new') }}" class="btn btn-primary">+ 新增第一个子网</a>{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user