Initial commit
This commit is contained in:
31
templates/accounts/login.html
Normal file
31
templates/accounts/login.html
Normal file
@@ -0,0 +1,31 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block title %}登录{% endblock %}
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h3 class="mb-3">登录</h3>
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<div class="mb-3">
|
||||
<label class="form-label">用户名或邮箱</label>
|
||||
{{ form.login }}
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">密码</label>
|
||||
{{ form.password }}
|
||||
</div>
|
||||
{% if captcha_question %}
|
||||
<div class="mb-3">
|
||||
<label class="form-label">验证码:<span class="text-primary">{{ captcha_question }}</span></label>
|
||||
{{ form.captcha }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="d-flex gap-2">
|
||||
<button class="btn btn-primary" type="submit">登录</button>
|
||||
<a class="btn btn-outline-secondary" href="{% url 'accounts:register' %}">注册</a>
|
||||
<a class="btn btn-outline-info" href="{% url 'accounts:password_change' %}">修改密码</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
20
templates/accounts/login_history.html
Normal file
20
templates/accounts/login_history.html
Normal file
@@ -0,0 +1,20 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block title %}登录历史{% endblock %}
|
||||
{% block content %}
|
||||
<h3 class="mb-3">最近登录历史</h3>
|
||||
<table class="table table-striped">
|
||||
<thead><tr><th>时间</th><th>IP</th><th>User-Agent</th></tr></thead>
|
||||
<tbody>
|
||||
{% for r in records %}
|
||||
<tr>
|
||||
<td>{{ r.created_at }}</td>
|
||||
<td>{{ r.ip_address|default:"-" }}</td>
|
||||
<td>{{ r.user_agent }}</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="3" class="text-muted">暂无记录</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<a class="btn btn-outline-secondary" href="{% url 'accounts:profile' %}">返回用户中心</a>
|
||||
{% endblock %}
|
||||
26
templates/accounts/password_change.html
Normal file
26
templates/accounts/password_change.html
Normal file
@@ -0,0 +1,26 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block title %}修改密码{% endblock %}
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h3 class="mb-3">修改密码</h3>
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<div class="mb-3">
|
||||
<label class="form-label">当前密码</label>
|
||||
{{ form.old_password }}
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">新密码</label>
|
||||
{{ form.new_password1 }}
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">确认新密码</label>
|
||||
{{ form.new_password2 }}
|
||||
</div>
|
||||
<button class="btn btn-primary" type="submit">保存</button>
|
||||
<a class="btn btn-outline-secondary ms-2" href="{% url 'accounts:profile' %}">返回用户中心</a>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
40
templates/accounts/profile.html
Normal file
40
templates/accounts/profile.html
Normal file
@@ -0,0 +1,40 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block title %}用户中心{% endblock %}
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h4 class="mb-3">资料维护</h4>
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<div class="mb-3">
|
||||
<label class="form-label">显示名称</label>
|
||||
{{ form.display_name }}
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">联系电话</label>
|
||||
{{ form.contact_phone }}
|
||||
</div>
|
||||
<button class="btn btn-primary" type="submit">保存</button>
|
||||
<a class="btn btn-outline-info ms-2" href="{% url 'accounts:password_change' %}">修改密码</a>
|
||||
<a class="btn btn-outline-secondary ms-2" href="{% url 'accounts:login_history' %}">登录历史</a>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h4 class="mb-3">最近登录记录</h4>
|
||||
<table class="table table-sm">
|
||||
<thead><tr><th>时间</th><th>IP</th><th>User-Agent</th></tr></thead>
|
||||
<tbody>
|
||||
{% for r in records %}
|
||||
<tr>
|
||||
<td>{{ r.created_at }}</td>
|
||||
<td>{{ r.ip_address|default:"-" }}</td>
|
||||
<td>{{ r.user_agent }}</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="3" class="text-muted">暂无登录记录</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
48
templates/accounts/register.html
Normal file
48
templates/accounts/register.html
Normal file
@@ -0,0 +1,48 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block title %}注册{% endblock %}
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-8">
|
||||
<h3 class="mb-3">注册账号</h3>
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">用户名</label>
|
||||
{{ form.username }}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">邮箱</label>
|
||||
{{ form.email }}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">密码</label>
|
||||
{{ form.password1 }}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">确认密码</label>
|
||||
{{ form.password2 }}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">显示名称</label>
|
||||
{{ form.display_name }}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">联系电话</label>
|
||||
{{ form.contact_phone }}
|
||||
</div>
|
||||
{% if captcha_question %}
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">验证码:<span class="text-primary">{{ captcha_question }}</span></label>
|
||||
{{ form.captcha }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
<button class="btn btn-success" type="submit">注册</button>
|
||||
<a class="btn btn-outline-secondary" href="{% url 'accounts:login' %}">已有账号?去登录</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
50
templates/admin_panel/dashboard.html
Normal file
50
templates/admin_panel/dashboard.html
Normal file
@@ -0,0 +1,50 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block title %}运营面板 - 概览{% endblock %}
|
||||
{% block content %}
|
||||
<div class="row g-3">
|
||||
<div class="col-md-3">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="text-muted">用户数量</div>
|
||||
<div class="h4">{{ users_count }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="text-muted">域名数量</div>
|
||||
<div class="h4">{{ domains_count }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="text-muted">套餐数量</div>
|
||||
<div class="h4">{{ plans_count }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="text-muted">未支付账单</div>
|
||||
<div class="h4">{{ unpaid_invoices }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<div class="btn-group">
|
||||
<a class="btn btn-primary" href="{% url 'admin_panel:settings' %}">系统设置</a>
|
||||
<a class="btn btn-outline-secondary" href="{% url 'admin_panel:plans' %}">套餐管理</a>
|
||||
<a class="btn btn-outline-secondary" href="{% url 'admin_panel:users' %}">用户管理</a>
|
||||
<a class="btn btn-outline-secondary" href="{% url 'admin_panel:domains' %}">域名管理</a>
|
||||
<a class="btn btn-outline-secondary" href="{% url 'admin_panel:billing_list' %}">账单管理</a>
|
||||
<a class="btn btn-outline-secondary" href="{% url 'admin_panel:quotas' %}">配额视图</a>
|
||||
<a class="btn btn-outline-secondary" href="{% url 'admin_panel:monitoring' %}">统计监控</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
28
templates/admin_panel/domain_grant_traffic.html
Normal file
28
templates/admin_panel/domain_grant_traffic.html
Normal file
@@ -0,0 +1,28 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block title %}运营面板 - 流量赠送{% endblock %}
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h3>为域名赠送本周期免费流量</h3>
|
||||
<div>
|
||||
<a class="btn btn-outline-secondary" href="{% url 'admin_panel:domains' %}">返回域名列表</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<strong>域名:</strong> {{ domain.name }}
|
||||
</div>
|
||||
<form method="post" class="row g-3">
|
||||
{% csrf_token %}
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">本周期额外赠送流量(GB)</label>
|
||||
{{ form.extra_free_traffic_gb_current_cycle }}
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<button class="btn btn-primary" type="submit">保存</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
29
templates/admin_panel/domain_plan_switch.html
Normal file
29
templates/admin_panel/domain_plan_switch.html
Normal file
@@ -0,0 +1,29 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block title %}运营面板 - 切换套餐{% endblock %}
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h3>切换套餐 - {{ domain.name }}</h3>
|
||||
<div>
|
||||
<a class="btn btn-outline-secondary" href="{% url 'admin_panel:domains' %}">返回域名列表</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="post" novalidate>
|
||||
{% csrf_token %}
|
||||
<div class="mb-3">
|
||||
<label class="form-label">当前套餐</label>
|
||||
{% if domain.current_plan %}
|
||||
<div class="form-control">{{ domain.current_plan.name }}</div>
|
||||
{% else %}
|
||||
<div class="form-control">-</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">选择新套餐</label>
|
||||
{{ form.current_plan }}
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary">确认切换</button>
|
||||
</form>
|
||||
{% endblock %}
|
||||
86
templates/admin_panel/domains_list.html
Normal file
86
templates/admin_panel/domains_list.html
Normal file
@@ -0,0 +1,86 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block title %}运营面板 - 域名管理{% endblock %}
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h3>域名管理</h3>
|
||||
<div>
|
||||
<a class="btn btn-outline-secondary" href="{% url 'admin_panel:dashboard' %}">返回概览</a>
|
||||
<a class="btn btn-outline-secondary" href="/admin/domains/domain/">在 Django Admin 查看</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="get" class="row g-2 mb-3">
|
||||
<div class="col-md-4">
|
||||
<input type="text" name="q" value="{{ q }}" class="form-control" placeholder="搜索域名" />
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<input type="number" name="user_id" value="{{ user_id }}" class="form-control" placeholder="用户ID(可选)" />
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<select name="status" class="form-select">
|
||||
<option value="">全部状态</option>
|
||||
{% for val,label in status_choices %}
|
||||
<option value="{{ val }}" {% if status == val %}selected{% endif %}>{{ label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<button class="btn btn-primary" type="submit">筛选</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>域名</th>
|
||||
<th>用户</th>
|
||||
<th>套餐</th>
|
||||
<th>状态</th>
|
||||
<th>ServerID</th>
|
||||
<th>更新时间</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for d in domains %}
|
||||
<tr>
|
||||
<td>{{ d.name }}</td>
|
||||
<td>{{ d.user.username }}</td>
|
||||
<td>{% if d.current_plan %}{{ d.current_plan.name }}{% else %}-{% endif %}</td>
|
||||
<td>
|
||||
{% if d.status == 'active' %}
|
||||
<span class="badge bg-success">正常</span>
|
||||
{% elif d.status == 'pending_dns' %}
|
||||
<span class="badge bg-info">等待DNS</span>
|
||||
{% elif d.status == 'suspended' %}
|
||||
<span class="badge bg-warning">暂停</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary">已删除</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{% if d.edge_server_id %}{{ d.edge_server_id }}{% else %}-{% endif %}</td>
|
||||
<td>{{ d.updated_at|date:'Y-m-d H:i' }}</td>
|
||||
<td class="text-nowrap">
|
||||
<a class="btn btn-sm btn-outline-primary me-2" href="{% url 'domains:detail' d.id %}" target="_blank">查看详情</a>
|
||||
<a class="btn btn-sm btn-outline-secondary me-2" href="{% url 'admin_panel:domain_switch_plan' d.id %}">切换套餐</a>
|
||||
<a class="btn btn-sm btn-outline-success me-2" href="{% url 'admin_panel:domain_grant_traffic' d.id %}">流量赠送</a>
|
||||
<form action="{% url 'admin_panel:domain_toggle_suspend' d.id %}" method="post" class="d-inline" onsubmit="return confirm('确定切换暂停/恢复吗?');">
|
||||
{% csrf_token %}
|
||||
<input type="text" name="confirm" class="form-control form-control-sm d-inline-block me-2" style="width: 160px;" placeholder="输入 CONFIRM" required>
|
||||
<button class="btn btn-sm btn-outline-warning">暂停/恢复</button>
|
||||
</form>
|
||||
<form action="{% url 'admin_panel:domain_delete' d.id %}" method="post" class="d-inline ms-2" onsubmit="return confirm('确认删除该域名?将标记为已删除');">
|
||||
{% csrf_token %}
|
||||
<input type="text" name="confirm" class="form-control form-control-sm d-inline-block me-2" style="width: 160px;" placeholder="输入 DELETE" required>
|
||||
<button class="btn btn-sm btn-outline-danger">删除</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="7" class="text-center">暂无域名</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endblock %}
|
||||
117
templates/admin_panel/invoice_detail.html
Normal file
117
templates/admin_panel/invoice_detail.html
Normal file
@@ -0,0 +1,117 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block title %}运营面板 - 账单详情{% endblock %}
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h3>账单详情 #{{ invoice.id }}</h3>
|
||||
<div>
|
||||
<a class="btn btn-outline-secondary" href="{% url 'admin_panel:billing_list' %}">返回账单列表</a>
|
||||
<a class="btn btn-outline-secondary" href="/admin/billing/invoice/{{ invoice.id }}/change/" target="_blank">在 Django Admin 编辑</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-3 mb-3">
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div><strong>用户:</strong> {{ invoice.user.username }}</div>
|
||||
<div><strong>周期:</strong> {{ invoice.period_start }} → {{ invoice.period_end }}</div>
|
||||
<div><strong>套餐费:</strong> ¥{{ invoice.amount_plan_total }}</div>
|
||||
<div><strong>超量费:</strong> ¥{{ invoice.amount_overage_total }}</div>
|
||||
<div><strong>调整:</strong> ¥{{ invoice.amount_adjustment }}</div>
|
||||
<div><strong>总金额:</strong> ¥{{ invoice.amount_total }}</div>
|
||||
<div><strong>状态:</strong>
|
||||
{% if invoice.status == 'unpaid' %}<span class="badge bg-warning">未支付</span>
|
||||
{% elif invoice.status == 'paid' %}<span class="badge bg-success">已支付</span>
|
||||
{% elif invoice.status == 'cancelled' %}<span class="badge bg-secondary">已取消</span>
|
||||
{% else %}<span class="badge bg-light text-dark">未知</span>{% endif %}
|
||||
</div>
|
||||
<div><strong>支付时间:</strong> {% if invoice.paid_at %}{{ invoice.paid_at|date:'Y-m-d H:i' }}{% else %}-{% endif %}</div>
|
||||
|
||||
<div class="mt-3">
|
||||
{% if invoice.status == 'unpaid' %}
|
||||
<form method="post" action="" onsubmit="return confirm('确认将此账单标记为已支付?');">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="mark_paid" />
|
||||
<button class="btn btn-success">标记为已支付</button>
|
||||
</form>
|
||||
<form method="post" action="" class="mt-2" onsubmit="return confirm('将对账单涉及的域名执行未支付策略(停服或限速),确认继续?');">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="apply_overage_policy" />
|
||||
<div class="mb-2"><input type="text" name="confirm" class="form-control form-control-sm" placeholder="输入 CONFIRM 以继续" required></div>
|
||||
<button class="btn btn-outline-danger">执行未支付策略</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">人工调账</h5>
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="add_adjustment" />
|
||||
<div class="mb-2">
|
||||
<label class="form-label">调整说明</label>
|
||||
{{ adj_form.description }}
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label">调整金额(¥)</label>
|
||||
{{ adj_form.amount }}
|
||||
</div>
|
||||
<div class="mb-2 form-check">
|
||||
{{ adj_form.is_increase }}
|
||||
<label class="form-check-label">增加金额(取消勾选为减少金额)</label>
|
||||
</div>
|
||||
<div class="mb-2"><input type="text" name="confirm" class="form-control form-control-sm" placeholder="输入 CONFIRM 以继续" required></div>
|
||||
<button class="btn btn-primary" type="submit">添加调整项</button>
|
||||
</form>
|
||||
|
||||
{% if invoice.status != 'cancelled' %}
|
||||
<hr />
|
||||
<form method="post" onsubmit="return confirm('确认取消此账单?');">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="cancel" />
|
||||
<div class="mb-2"><input type="text" name="confirm" class="form-control form-control-sm" placeholder="输入 CONFIRM 以继续" required></div>
|
||||
<button class="btn btn-outline-danger">取消账单</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">账单明细</h5>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>域名</th>
|
||||
<th>说明</th>
|
||||
<th>数量</th>
|
||||
<th>单价(¥)</th>
|
||||
<th>金额(¥)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for it in items %}
|
||||
<tr>
|
||||
<td>{% if it.domain %}{{ it.domain.name }}{% else %}-{% endif %}</td>
|
||||
<td>{{ it.description }}</td>
|
||||
<td>{{ it.quantity }}</td>
|
||||
<td>{{ it.unit_price }}</td>
|
||||
<td>{{ it.amount }}</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="5" class="text-center">暂无明细</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
75
templates/admin_panel/invoices_list.html
Normal file
75
templates/admin_panel/invoices_list.html
Normal file
@@ -0,0 +1,75 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block title %}运营面板 - 账单列表{% endblock %}
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h3>账单管理</h3>
|
||||
<div>
|
||||
<a class="btn btn-outline-secondary" href="{% url 'admin_panel:dashboard' %}">返回概览</a>
|
||||
<a class="btn btn-outline-secondary" href="/admin/billing/invoice/" target="_blank">在 Django Admin 查看</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="get" class="row g-2 mb-3">
|
||||
<div class="col-md-3">
|
||||
<select name="status" class="form-select">
|
||||
<option value="">全部状态</option>
|
||||
<option value="unpaid" {% if status == 'unpaid' %}selected{% endif %}>未支付</option>
|
||||
<option value="paid" {% if status == 'paid' %}selected{% endif %}>已支付</option>
|
||||
<option value="cancelled" {% if status == 'cancelled' %}selected{% endif %}>已取消</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<input type="number" name="user_id" value="{{ user_id }}" class="form-control" placeholder="用户ID(可选)" />
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<button class="btn btn-primary" type="submit">筛选</button>
|
||||
</div>
|
||||
<div class="col-md-4 text-end">
|
||||
<span class="text-muted">当前筛选总金额:¥{{ total_amount }}</span>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>用户</th>
|
||||
<th>周期</th>
|
||||
<th>套餐费</th>
|
||||
<th>超量费</th>
|
||||
<th>调整</th>
|
||||
<th>总金额</th>
|
||||
<th>状态</th>
|
||||
<th>支付时间</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for inv in invoices %}
|
||||
<tr>
|
||||
<td>{{ inv.id }}</td>
|
||||
<td>{{ inv.user.username }}</td>
|
||||
<td>{{ inv.period_start }} → {{ inv.period_end }}</td>
|
||||
<td>¥{{ inv.amount_plan_total }}</td>
|
||||
<td>¥{{ inv.amount_overage_total }}</td>
|
||||
<td>¥{{ inv.amount_adjustment }}</td>
|
||||
<td><strong>¥{{ inv.amount_total }}</strong></td>
|
||||
<td>
|
||||
{% if inv.status == 'unpaid' %}<span class="badge bg-warning">未支付</span>
|
||||
{% elif inv.status == 'paid' %}<span class="badge bg-success">已支付</span>
|
||||
{% elif inv.status == 'cancelled' %}<span class="badge bg-secondary">已取消</span>
|
||||
{% else %}<span class="badge bg-light text-dark">未知</span>{% endif %}
|
||||
</td>
|
||||
<td>{% if inv.paid_at %}{{ inv.paid_at|date:'Y-m-d H:i' }}{% else %}-{% endif %}</td>
|
||||
<td>
|
||||
<a class="btn btn-sm btn-outline-primary" href="{% url 'admin_panel:billing_detail' inv.id %}">详情/调账</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="10" class="text-center">暂无账单</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endblock %}
|
||||
154
templates/admin_panel/monitoring.html
Normal file
154
templates/admin_panel/monitoring.html
Normal file
@@ -0,0 +1,154 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block title %}运营面板 - 统计与监控{% endblock %}
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h3>统计与监控</h3>
|
||||
<div>
|
||||
<a class="btn btn-outline-secondary" href="{% url 'admin_panel:dashboard' %}">返回概览</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="get" class="row g-2 mb-3">
|
||||
<div class="col-md-3">
|
||||
<input type="number" name="days" value="{{ days }}" class="form-control" placeholder="展示天数(默认14)" />
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<button class="btn btn-primary" type="submit">刷新</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">平台每日总流量(GB)</h5>
|
||||
<canvas id="platformDailyChart" height="120" class="mb-3" style="width: 100%;"></canvas>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-striped">
|
||||
<thead>
|
||||
<tr><th>日期</th><th>GB</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for r in daily_rows %}
|
||||
<tr><td>{{ r.day }}</td><td>{{ r.gb }}</td></tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="2" class="text-center">暂无数据</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">本月域名流量 Top 20(GB)</h5>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-striped">
|
||||
<thead>
|
||||
<tr><th>域名</th><th>GB</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for t in top_domains %}
|
||||
<tr><td>{{ t.domain.name }}</td><td>{{ t.gb }}</td></tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="2" class="text-center">暂无数据</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-3 mt-1">
|
||||
<div class="col-md-12">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<h5 class="card-title mb-0">异常域名(根据阈值检测)</h5>
|
||||
<div class="text-muted small">
|
||||
阈值:倍率 {{ anomaly_config.multiplier }} ×,窗口 {{ anomaly_config.window_days }} 天,最低 {{ anomaly_config.min_gb }} GB
|
||||
,<a href="{% url 'admin_panel:settings' %}">调整参数</a>
|
||||
</div>
|
||||
</div>
|
||||
{% if not anomaly_config.enabled %}
|
||||
<div class="alert alert-secondary">异常检测已关闭,可在系统设置中开启。</div>
|
||||
{% endif %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-striped">
|
||||
<thead>
|
||||
<tr><th>域名</th><th>今日GB</th><th>过去均值GB</th><th>倍率</th><th>查看</th><th>操作</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for a in anomalies %}
|
||||
<tr class="table-warning">
|
||||
<td>{{ a.domain.name }}</td>
|
||||
<td>{{ a.today_gb }}</td>
|
||||
<td>{{ a.past_avg_gb }}</td>
|
||||
<td>{% if a.ratio %}×{{ a.ratio }}{% else %}-{% endif %}</td>
|
||||
<td>
|
||||
<a class="btn btn-sm btn-outline-primary" href="/domains/{{ a.domain.id }}/">用户侧详情</a>
|
||||
</td>
|
||||
<td>
|
||||
<form method="post" action="{% url 'admin_panel:domain_toggle_suspend' a.domain.id %}" class="d-inline" onsubmit="return confirm('确定切换暂停/恢复该域名吗?');">
|
||||
{% csrf_token %}
|
||||
<button class="btn btn-sm btn-outline-warning" type="submit">暂停/恢复</button>
|
||||
</form>
|
||||
<a class="btn btn-sm btn-outline-secondary" href="{% url 'admin_panel:domain_switch_plan' a.domain.id %}">切换套餐</a>
|
||||
<a class="btn btn-sm btn-outline-success" href="{% url 'admin_panel:domain_grant_traffic' a.domain.id %}">流量赠送</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="6" class="text-center">暂无异常域名</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function(){
|
||||
var rows = [
|
||||
{% for r in daily_rows %}
|
||||
{ day: '{{ r.day|date:"Y-m-d" }}', gb: {{ r.gb|default:0 }} },
|
||||
{% endfor %}
|
||||
];
|
||||
var el = document.getElementById('platformDailyChart');
|
||||
if (!el || !rows.length || !window.Chart) return;
|
||||
var ctx = el.getContext('2d');
|
||||
var labels = rows.map(function(x){ return x.day; });
|
||||
var dataGb = rows.map(function(x){ return x.gb || 0; });
|
||||
new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: labels,
|
||||
datasets: [{
|
||||
label: '平台总流量(GB)',
|
||||
data: dataGb,
|
||||
borderColor: '#198754',
|
||||
backgroundColor: 'rgba(25,135,84,0.15)',
|
||||
fill: true,
|
||||
tension: 0.3
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
interaction: { mode: 'index', intersect: false },
|
||||
plugins: {
|
||||
legend: { position: 'bottom' },
|
||||
tooltip: { callbacks: { label: function(ctx){ return ctx.dataset.label + ': ' + ctx.formattedValue + ' GB'; } } }
|
||||
},
|
||||
scales: {
|
||||
y: { title: { display: true, text: 'GB' } }
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
90
templates/admin_panel/operation_logs.html
Normal file
90
templates/admin_panel/operation_logs.html
Normal file
@@ -0,0 +1,90 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block title %}运营面板 - 操作日志{% endblock %}
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h3>操作日志</h3>
|
||||
<div class="btn-group">
|
||||
<a class="btn btn-outline-secondary" href="{% url 'admin_panel:dashboard' %}">返回概览</a>
|
||||
<a class="btn btn-outline-primary" href="?actor={{ actor }}&action={{ action }}&target={{ target }}&start={{ start }}&end={{ end }}&export=csv">导出 CSV</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="get" class="card mb-3">
|
||||
<div class="card-body">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-2">
|
||||
<label class="form-label">操作人</label>
|
||||
<input type="text" name="actor" value="{{ actor }}" class="form-control" placeholder="用户名">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label class="form-label">动作</label>
|
||||
<input type="text" name="action" value="{{ action }}" class="form-control" placeholder="如 upgrade_plan">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">目标</label>
|
||||
<input type="text" name="target" value="{{ target }}" class="form-control" placeholder="域名或对象">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label class="form-label">开始时间</label>
|
||||
<input type="text" name="start" value="{{ start }}" class="form-control" placeholder="YYYY-MM-DD">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label class="form-label">结束时间</label>
|
||||
<input type="text" name="end" value="{{ end }}" class="form-control" placeholder="YYYY-MM-DD">
|
||||
</div>
|
||||
<div class="col-md-1 d-flex align-items-end">
|
||||
<button class="btn btn-primary w-100" type="submit">筛选</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>时间</th>
|
||||
<th>操作人</th>
|
||||
<th>动作</th>
|
||||
<th>目标</th>
|
||||
<th>详情</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for r in page_obj.object_list %}
|
||||
<tr>
|
||||
<td>{{ r.created_at }}</td>
|
||||
<td>{% if r.actor %}{{ r.actor.username }}{% else %}<span class="text-muted">-</span>{% endif %}</td>
|
||||
<td><span class="badge text-bg-secondary">{{ r.action }}</span></td>
|
||||
<td>{{ r.target }}</td>
|
||||
<td>
|
||||
{% if r.detail %}
|
||||
<code class="small">{{ r.detail|truncatechars:200 }}</code>
|
||||
{% else %}
|
||||
<span class="text-muted">(无)</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="5" class="text-muted">暂无数据</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<nav>
|
||||
<ul class="pagination">
|
||||
{% if page_obj.has_previous %}
|
||||
<li class="page-item"><a class="page-link" href="?page={{ page_obj.previous_page_number }}&actor={{ actor }}&action={{ action }}&target={{ target }}&start={{ start }}&end={{ end }}">上一页</a></li>
|
||||
{% else %}
|
||||
<li class="page-item disabled"><span class="page-link">上一页</span></li>
|
||||
{% endif %}
|
||||
<li class="page-item active"><span class="page-link">{{ page_obj.number }} / {{ page_obj.paginator.num_pages }}</span></li>
|
||||
{% if page_obj.has_next %}
|
||||
<li class="page-item"><a class="page-link" href="?page={{ page_obj.next_page_number }}&actor={{ actor }}&action={{ action }}&target={{ target }}&start={{ start }}&end={{ end }}">下一页</a></li>
|
||||
{% else %}
|
||||
<li class="page-item disabled"><span class="page-link">下一页</span></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
{% endblock %}
|
||||
104
templates/admin_panel/plan_form.html
Normal file
104
templates/admin_panel/plan_form.html
Normal file
@@ -0,0 +1,104 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block title %}{% if is_edit %}编辑套餐{% else %}新建套餐{% endif %}{% endblock %}
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h3>{% if is_edit %}编辑套餐{% else %}新建套餐{% endif %}</h3>
|
||||
<div>
|
||||
<a class="btn btn-outline-secondary" href="{% url 'admin_panel:plans' %}">返回套餐列表</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="post" novalidate>
|
||||
{% csrf_token %}
|
||||
<div class="mb-3">
|
||||
<label class="form-label">名称</label>
|
||||
{{ form.name }}
|
||||
{% if form.name.errors %}<div class="text-danger small">{{ form.name.errors|join:', ' }}</div>{% endif %}
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">描述</label>
|
||||
{{ form.description }}
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-4 mb-3">
|
||||
<label class="form-label">计费模式</label>
|
||||
{{ form.billing_mode }}
|
||||
</div>
|
||||
<div class="col-md-4 mb-3">
|
||||
<label class="form-label">月费(每域名)</label>
|
||||
{{ form.base_price_per_domain }}
|
||||
</div>
|
||||
<div class="col-md-4 mb-3">
|
||||
<label class="form-label">包含流量(GB)</label>
|
||||
{{ form.included_traffic_gb_per_domain }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-4 mb-3">
|
||||
<label class="form-label">超量单价(元/GB)</label>
|
||||
{{ form.overage_price_per_gb }}
|
||||
</div>
|
||||
<div class="col-md-8 mb-3">
|
||||
<label class="form-label">功能配置(JSON)</label>
|
||||
{{ form.features }}
|
||||
<div class="form-text">示例:{"waf_enabled": true, "http3_enabled": false}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<h5 class="mb-2">可视化功能(推荐)</h5>
|
||||
<div class="row gx-3 gy-2">
|
||||
<div class="col-md-2 form-check">
|
||||
{{ form.waf_enabled }}
|
||||
<label class="form-check-label">启用 WAF</label>
|
||||
</div>
|
||||
<div class="col-md-2 form-check">
|
||||
{{ form.http3_enabled }}
|
||||
<label class="form-check-label">启用 HTTP/3</label>
|
||||
</div>
|
||||
<div class="col-md-2 form-check">
|
||||
{{ form.websocket_enabled }}
|
||||
<label class="form-check-label">启用 WebSocket</label>
|
||||
</div>
|
||||
<div class="col-md-2 form-check">
|
||||
{{ form.realtime_logs_enabled }}
|
||||
<label class="form-check-label">实时日志</label>
|
||||
</div>
|
||||
<div class="col-md-2 form-check">
|
||||
{{ form.custom_ssl_enabled }}
|
||||
<label class="form-check-label">自定义 SSL</label>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label class="form-label">页面规则数量上限</label>
|
||||
{{ form.page_rules_limit }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-text mt-1">勾选将同步写入 JSON 字段;可选 JSON 保留高级配置。</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-2 form-check">
|
||||
{{ form.allow_overage }}
|
||||
<label class="form-check-label">允许超量计费</label>
|
||||
</div>
|
||||
<div class="col-md-2 form-check">
|
||||
{{ form.is_active }}
|
||||
<label class="form-check-label">启用</label>
|
||||
</div>
|
||||
<div class="col-md-2 form-check">
|
||||
{{ form.is_public }}
|
||||
<label class="form-check-label">公开</label>
|
||||
</div>
|
||||
<div class="col-md-3 form-check">
|
||||
{{ form.allow_new_purchase }}
|
||||
<label class="form-check-label">允许新购</label>
|
||||
</div>
|
||||
<div class="col-md-3 form-check">
|
||||
{{ form.allow_renew }}
|
||||
<label class="form-check-label">允许续费</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary">{% if is_edit %}保存修改{% else %}创建套餐{% endif %}</button>
|
||||
</form>
|
||||
{% endblock %}
|
||||
74
templates/admin_panel/plans_list.html
Normal file
74
templates/admin_panel/plans_list.html
Normal file
@@ -0,0 +1,74 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block title %}运营面板 - 套餐管理{% endblock %}
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h3>套餐管理</h3>
|
||||
<div>
|
||||
<a href="{% url 'admin_panel:plan_create' %}" class="btn btn-primary me-2">新建套餐</a>
|
||||
<a class="btn btn-outline-secondary me-2" href="{% url 'admin_panel:dashboard' %}">返回概览</a>
|
||||
<a class="btn btn-outline-secondary" href="/admin/">在 Django Admin 编辑</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>名称</th>
|
||||
<th>月费</th>
|
||||
<th>包含流量(GB)</th>
|
||||
<th>超量单价</th>
|
||||
<th>状态</th>
|
||||
<th>公开</th>
|
||||
<th>允许新购</th>
|
||||
<th>允许续费</th>
|
||||
<th>功能摘要</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for p in plans %}
|
||||
<tr>
|
||||
<td>{{ p.name }}</td>
|
||||
<td>{{ p.base_price_per_domain }}</td>
|
||||
<td>{{ p.included_traffic_gb_per_domain }}</td>
|
||||
<td>{{ p.overage_price_per_gb }}</td>
|
||||
<td>{% if p.is_active %}<span class="badge bg-success">Active</span>{% else %}<span class="badge bg-secondary">Disabled</span>{% endif %}</td>
|
||||
<td>{% if p.is_public %}是{% else %}否{% endif %}</td>
|
||||
<td>{% if p.allow_new_purchase %}是{% else %}否{% endif %}</td>
|
||||
<td>{% if p.allow_renew %}是{% else %}否{% endif %}</td>
|
||||
<td>
|
||||
{% if p.features %}
|
||||
<span class="badge bg-light text-dark me-1">WAF {% if p.features.waf_enabled %}✔{% else %}✖{% endif %}</span>
|
||||
<span class="badge bg-light text-dark me-1">HTTP/3 {% if p.features.http3_enabled %}✔{% else %}✖{% endif %}</span>
|
||||
<span class="badge bg-light text-dark me-1">WebSocket {% if p.features.websocket_enabled %}✔{% else %}✖{% endif %}</span>
|
||||
<span class="badge bg-light text-dark me-1">日志 {% if p.features.realtime_logs_enabled or p.features.logs_enabled %}✔{% else %}✖{% endif %}</span>
|
||||
{% else %}
|
||||
<span class="text-muted">-</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-nowrap">
|
||||
<a class="btn btn-sm btn-outline-primary me-2" href="{% url 'admin_panel:plan_edit' p.id %}">编辑</a>
|
||||
<form action="{% url 'admin_panel:plan_toggle_active' p.id %}" method="post" class="d-inline" onsubmit="return confirm('确定切换启用状态吗?');">
|
||||
{% csrf_token %}
|
||||
<button class="btn btn-sm btn-outline-warning">切换启用</button>
|
||||
</form>
|
||||
<form action="{% url 'admin_panel:plan_toggle_public' p.id %}" method="post" class="d-inline" onsubmit="return confirm('确定切换公开状态吗?');">
|
||||
{% csrf_token %}
|
||||
<button class="btn btn-sm btn-outline-secondary">切换公开</button>
|
||||
</form>
|
||||
<form action="{% url 'admin_panel:plan_toggle_allow_new' p.id %}" method="post" class="d-inline" onsubmit="return confirm('确定切换允许新购吗?');">
|
||||
{% csrf_token %}
|
||||
<button class="btn btn-sm btn-outline-info">切换新购</button>
|
||||
</form>
|
||||
<form action="{% url 'admin_panel:plan_toggle_allow_renew' p.id %}" method="post" class="d-inline" onsubmit="return confirm('确定切换允许续费吗?');">
|
||||
{% csrf_token %}
|
||||
<button class="btn btn-sm btn-outline-success">切换续费</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="9" class="text-center">暂无套餐</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endblock %}
|
||||
50
templates/admin_panel/quotas.html
Normal file
50
templates/admin_panel/quotas.html
Normal file
@@ -0,0 +1,50 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block title %}运营面板 - 配额视图{% endblock %}
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h3>配额视图(全局 / 套餐 / 用户 / 域名额外)</h3>
|
||||
<div>
|
||||
<a class="btn btn-outline-secondary" href="{% url 'admin_panel:dashboard' %}">返回概览</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="get" class="row g-2 mb-3">
|
||||
<div class="col-md-3">
|
||||
<input type="number" name="user_id" value="{{ user_id }}" class="form-control" placeholder="用户ID(可选)" />
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<button class="btn btn-primary" type="submit">筛选</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>域名</th>
|
||||
<th>用户</th>
|
||||
<th>套餐流量(GB)</th>
|
||||
<th>用户默认覆盖(GB)</th>
|
||||
<th>全局默认(GB)</th>
|
||||
<th>域名本周期额外(GB)</th>
|
||||
<th>总可用(GB)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for r in rows %}
|
||||
<tr>
|
||||
<td>{{ r.domain.name }}</td>
|
||||
<td>{{ r.domain.user.username }}</td>
|
||||
<td>{{ r.plan_quota }}</td>
|
||||
<td>{{ r.user_override }}</td>
|
||||
<td>{{ r.global_default }}</td>
|
||||
<td>{{ r.domain_extra }}</td>
|
||||
<td><strong>{{ r.total_quota }}</strong></td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="7" class="text-center">暂无数据</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endblock %}
|
||||
143
templates/admin_panel/settings.html
Normal file
143
templates/admin_panel/settings.html
Normal file
@@ -0,0 +1,143 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block title %}运营面板 - 系统设置{% endblock %}
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h3>系统设置</h3>
|
||||
<a class="btn btn-outline-secondary" href="{% url 'admin_panel:dashboard' %}">返回概览</a>
|
||||
</div>
|
||||
|
||||
<form method="post" class="mb-3">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="save" />
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">GoEdge Admin API 地址</label>
|
||||
{{ form.goedge_base_url }}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Admin AccessKeyId</label>
|
||||
{{ form.admin_access_key_id }}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Admin AccessKey</label>
|
||||
{{ form.admin_access_key }}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">默认节点集群ID</label>
|
||||
{{ form.default_node_cluster_id }}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">每域名默认免费流量(GB/月)</label>
|
||||
{{ form.default_free_traffic_gb_per_domain }}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">默认访问日志策略ID</label>
|
||||
{{ form.default_http_access_log_policy_id }}
|
||||
<div class="form-text">用于为域名启用访问日志时引用(HTTPAccessLogPolicyId)。</div>
|
||||
<div class="mt-2">
|
||||
<button class="btn btn-sm btn-outline-primary" type="submit" name="action" value="validate_access_log_policy">校验访问日志策略</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">默认WAF策略ID</label>
|
||||
{{ form.default_http_firewall_policy_id }}
|
||||
<div class="form-text">用于为域名启用WAF时引用(HTTPFirewallPolicyId)。</div>
|
||||
<div class="mt-2">
|
||||
<button class="btn btn-sm btn-outline-danger" type="submit" name="action" value="validate_firewall_policy">校验WAF策略</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">CNAME 模板</label>
|
||||
{{ form.cname_template }}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">默认未支付动作</label>
|
||||
{{ form.overage_action }}
|
||||
<div class="form-text">未支付账单执行的处理:停服或限速。</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">限速值(字节/秒)</label>
|
||||
{{ form.overage_limit_bps }}
|
||||
<div class="form-text">选择限速时生效;为 0 表示不启用限速。</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">启用异常流量检测</label>
|
||||
<div class="form-check">
|
||||
{{ form.anomaly_detection_enabled }}
|
||||
<label class="form-check-label">开启后在监控页标记异常域名</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">启用登录/注册验证码</label>
|
||||
<div class="form-check">
|
||||
{{ form.captcha_enabled }}
|
||||
<label class="form-check-label">开启后用户登录/注册需输入简单验证码</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">易支付 API 地址</label>
|
||||
{{ form.epay_api_base_url }}
|
||||
<div class="form-text">例如:https://api.example.com,统一下单使用 submit.php</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">易支付商户ID</label>
|
||||
{{ form.epay_pid }}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">易支付密钥</label>
|
||||
{{ form.epay_key }}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">异常阈值倍率</label>
|
||||
{{ form.anomaly_threshold_multiplier }}
|
||||
<div class="form-text">今日 GB ≥ 过去均值 × 倍率 → 异常</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">均值窗口天数</label>
|
||||
{{ form.anomaly_window_days }}
|
||||
<div class="form-text">过去 N 天均值(不含今日)</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">最低触发 GB</label>
|
||||
{{ form.anomaly_min_gb }}
|
||||
<div class="form-text">低于该值不标记异常(去除小流量噪声)</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 d-flex gap-2">
|
||||
<button class="btn btn-primary" type="submit">保存设置</button>
|
||||
<button class="btn btn-outline-secondary" type="submit" formaction="" name="action" value="get_token">获取 AccessToken</button>
|
||||
<button class="btn btn-outline-success" type="submit" formaction="" name="action" value="test_connection">测试连接</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">当前令牌信息</div>
|
||||
<div class="card-body">
|
||||
<div>AccessToken:{% if settings_obj.edge_access_token %}<code>{{ settings_obj.edge_access_token }}</code>{% else %}-{% endif %}</div>
|
||||
<div>过期时间:{% if settings_obj.edge_token_expires_at %}{{ settings_obj.edge_token_expires_at }}{% else %}-{% endif %}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mt-3">
|
||||
<div class="card-header">运行指南</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-2">建议定时任务(示例命令):</div>
|
||||
<ul class="mb-3">
|
||||
<li>每日 02:00 拉取统计:<code>python manage.py pull_daily_stats --days 1</code></li>
|
||||
<li>每月第 1 天 01:00 生成账单:<code>python manage.py generate_invoices --overwrite</code></li>
|
||||
<li>每日 03:00 执行未支付策略:<code>python manage.py apply_invoice_policies --days-overdue 3</code></li>
|
||||
</ul>
|
||||
<div class="mb-2">Windows 任务计划建议:</div>
|
||||
<ul class="mb-3">
|
||||
<li>使用“任务计划程序”创建任务,触发器设置为上述时间,操作为运行 <code>python.exe</code>,参数为对应 <code>manage.py</code> 命令。</li>
|
||||
</ul>
|
||||
<div class="mb-2">常见故障排查:</div>
|
||||
<ul>
|
||||
<li>GoEdge 连接失败:检查 <code>GoEdge Admin API 地址</code>、AccessKeyId/AccessKey 或当前 AccessToken 是否有效。</li>
|
||||
<li>MySQL 连接异常:确认数据库账号/主机/端口,并安装 <code>pymysql</code>;查看系统日志以定位 <code>MySQLdb</code> 安装失败。</li>
|
||||
<li>访问日志为空:确保在域名设置中启用“实时日志”,并在系统设置配置访问日志策略 ID。</li>
|
||||
<li>未支付策略不生效:检查系统设置中的默认未支付策略(动作及限速值)。</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
55
templates/admin_panel/users_list.html
Normal file
55
templates/admin_panel/users_list.html
Normal file
@@ -0,0 +1,55 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block title %}运营面板 - 用户管理{% endblock %}
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h3>用户管理</h3>
|
||||
<div>
|
||||
<a class="btn btn-outline-secondary" href="{% url 'admin_panel:dashboard' %}">返回概览</a>
|
||||
<a class="btn btn-outline-secondary" href="/admin/auth/user/">在 Django Admin 查看</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="get" class="row g-2 mb-3">
|
||||
<div class="col-md-4">
|
||||
<input type="text" name="q" value="{{ q }}" class="form-control" placeholder="搜索用户名或邮箱" />
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<button class="btn btn-primary" type="submit">搜索</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>用户名</th>
|
||||
<th>显示名</th>
|
||||
<th>邮箱</th>
|
||||
<th>状态</th>
|
||||
<th>是否运营</th>
|
||||
<th>域名数</th>
|
||||
<th>注册时间</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for u in users %}
|
||||
<tr>
|
||||
<td>{{ u.username }}</td>
|
||||
<td>{% if u.profile %}{{ u.profile.display_name }}{% endif %}</td>
|
||||
<td>{{ u.email }}</td>
|
||||
<td>{% if u.is_active %}<span class="badge bg-success">Active</span>{% else %}<span class="badge bg-secondary">Disabled</span>{% endif %}</td>
|
||||
<td>{% if u.is_staff %}是{% else %}否{% endif %}</td>
|
||||
<td>{{ u.domain_count }}</td>
|
||||
<td>{{ u.date_joined|date:'Y-m-d H:i' }}</td>
|
||||
<td>
|
||||
<a class="btn btn-sm btn-outline-primary" href="{% url 'admin_panel:domains' %}?user_id={{ u.id }}">查看域名</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="8" class="text-center">暂无用户</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endblock %}
|
||||
61
templates/base.html
Normal file
61
templates/base.html
Normal file
@@ -0,0 +1,61 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{% block title %}PyGoEdge 面板{% endblock %}</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
{% block extra_head %}{% endblock %}
|
||||
<style>
|
||||
.navbar-brand small { font-size: 0.8rem; opacity: .8; }
|
||||
</style>
|
||||
{% load static %}
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark mb-4">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="/domains/">PyGoEdge 面板 <small>Beta</small></a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#topNav" aria-controls="topNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="topNav">
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||
<li class="nav-item"><a class="nav-link" href="/domains/">域名</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/plans/">套餐</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/billing/">账单</a></li>
|
||||
{% if request.user.is_staff %}
|
||||
<li class="nav-item"><a class="nav-link" href="/admin-panel/">运营面板</a></li>
|
||||
{% endif %}
|
||||
<li class="nav-item"><a class="nav-link" href="/admin/">Django Admin</a></li>
|
||||
</ul>
|
||||
<div class="d-flex align-items-center text-light">
|
||||
{% if request.user.is_authenticated %}
|
||||
<span class="me-2">你好,{{ request.user.username }}</span>
|
||||
<a class="btn btn-outline-light btn-sm me-2" href="/accounts/profile/">用户中心</a>
|
||||
<form method="post" action="/accounts/logout/" class="d-inline">
|
||||
{% csrf_token %}
|
||||
<button class="btn btn-warning btn-sm" type="submit">退出</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<a class="btn btn-outline-light btn-sm me-2" href="/accounts/login/">登录</a>
|
||||
<a class="btn btn-success btn-sm" href="/accounts/register/">注册</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="container">
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
<div class="alert alert-{{ message.tags }}">{{ message }}</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||
{% block extra_scripts %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
96
templates/billing/detail.html
Normal file
96
templates/billing/detail.html
Normal file
@@ -0,0 +1,96 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block title %}账单详情{% endblock %}
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<div>
|
||||
<h3>账单详情</h3>
|
||||
<div class="text-muted">周期:{{ invoice.period_start }} ~ {{ invoice.period_end }}</div>
|
||||
<div class="text-muted">状态:{{ invoice.get_status_display }}</div>
|
||||
</div>
|
||||
<div>
|
||||
{% if invoice.status == 'unpaid' %}
|
||||
<form method="post" onsubmit="return confirm('确认将该账单标记为已支付?');">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="mark_paid" />
|
||||
<button type="submit" class="btn btn-success">标记为已支付</button>
|
||||
</form>
|
||||
<a class="btn btn-primary ms-2" href="{% url 'billing:pay' invoice.id %}">立即支付</a>
|
||||
{% endif %}
|
||||
<a class="btn btn-outline-secondary" href="{% url 'billing:detail_csv' invoice.id %}">导出 CSV</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">费用汇总</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-3">套餐费用:¥ {{ plan_total }}</div>
|
||||
<div class="col-md-3">超量费用:¥ {{ overage_total }}</div>
|
||||
<div class="col-md-3">调整:¥ {{ adjustment }}</div>
|
||||
<div class="col-md-3"><strong>总金额:¥ {{ amount_total }}</strong></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>域名</th>
|
||||
<th>描述</th>
|
||||
<th class="text-end">数量(GB)</th>
|
||||
<th class="text-end">单价</th>
|
||||
<th class="text-end">金额</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for it in items %}
|
||||
<tr>
|
||||
<td>{% if it.domain %}<a href="/domains/{{ it.domain.id }}/">{{ it.domain.name }}</a>{% else %}-{% endif %}</td>
|
||||
<td>
|
||||
<div>{{ it.description }}</div>
|
||||
{% if it.description == '基础套餐费用' %}
|
||||
<div class="text-muted small">每域名当月基础月费。</div>
|
||||
{% elif it.description == '超量流量费用' %}
|
||||
<div class="text-muted small">超出包含/免费额度的部分,按单价计费。</div>
|
||||
{% elif it.description %}
|
||||
<div class="text-muted small">{{ it.description }}</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-end">{{ it.quantity }}</td>
|
||||
<td class="text-end">¥ {{ it.unit_price }}</td>
|
||||
<td class="text-end"><strong>¥ {{ it.amount }}</strong></td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="5" class="text-center">暂无账单项</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">账单周期流量统计(引用)</div>
|
||||
<div class="card-body">
|
||||
<div class="text-muted mb-2">周期:{{ period_start }} ~ {{ period_end }},统计为参考,可能与套餐变更及赠送额度生效时间相关。</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-striped">
|
||||
<thead>
|
||||
<tr><th>域名</th><th>用量(GB)</th><th>查看详情</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for s in domain_stats %}
|
||||
<tr>
|
||||
<td>{{ s.domain.name }}</td>
|
||||
<td>{{ s.gb }}</td>
|
||||
<td><a class="btn btn-sm btn-outline-primary" href="/domains/{{ s.domain.id }}/">用户侧详情</a></td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="3" class="text-center">暂无数据</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="text-muted small">当前暂未包含请求数/状态码分布的数据库统计,如需请在“域名详情 → 统计”查看。</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a href="/billing/" class="btn btn-outline-secondary">返回列表</a>
|
||||
{% endblock %}
|
||||
66
templates/billing/list.html
Normal file
66
templates/billing/list.html
Normal file
@@ -0,0 +1,66 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block title %}账单列表{% endblock %}
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h3>账单列表</h3>
|
||||
<div class="text-muted">总金额:¥ {{ total_amount }}</div>
|
||||
</div>
|
||||
|
||||
<form method="get" class="row g-2 mb-3">
|
||||
<div class="col-md-3">
|
||||
<select name="status" class="form-select">
|
||||
<option value="">全部状态</option>
|
||||
{% for val,label in status_choices %}
|
||||
<option value="{{ val }}" {% if status == val %}selected{% endif %}>{{ label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<button class="btn btn-primary" type="submit">筛选</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>周期</th>
|
||||
<th>套餐费用</th>
|
||||
<th>超量费用</th>
|
||||
<th>调整</th>
|
||||
<th>总金额</th>
|
||||
<th>状态</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for inv in invoices %}
|
||||
<tr>
|
||||
<td>{{ inv.period_start }} ~ {{ inv.period_end }}</td>
|
||||
<td>¥ {{ inv.amount_plan_total }}</td>
|
||||
<td>¥ {{ inv.amount_overage_total }}</td>
|
||||
<td>¥ {{ inv.amount_adjustment }}</td>
|
||||
<td><strong>¥ {{ inv.amount_total }}</strong></td>
|
||||
<td>
|
||||
{% if inv.status == 'unpaid' %}
|
||||
<span class="badge bg-warning text-dark">未支付</span>
|
||||
{% elif inv.status == 'paid' %}
|
||||
<span class="badge bg-success">已支付</span>
|
||||
{% elif inv.status == 'cancelled' %}
|
||||
<span class="badge bg-secondary">已取消</span>
|
||||
{% else %}
|
||||
{{ inv.get_status_display }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<a href="{% url 'billing:detail' inv.id %}" class="btn btn-sm btn-outline-secondary">详情</a>
|
||||
{% if inv.status == 'unpaid' %}
|
||||
<a href="{% url 'billing:pay' inv.id %}" class="btn btn-sm btn-primary ms-1">支付</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="7" class="text-center">暂无账单</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endblock %}
|
||||
72
templates/domains/add.html
Normal file
72
templates/domains/add.html
Normal file
@@ -0,0 +1,72 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block title %}添加域名{% endblock %}
|
||||
{% block content %}
|
||||
<div class="container" style="max-width: 860px;">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h3>添加域名</h3>
|
||||
<div>
|
||||
<a class="btn btn-outline-secondary" href="{% url 'domains:list' %}">返回列表</a>
|
||||
<a class="btn btn-outline-secondary" href="/plans/">查看套餐</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
<div class="alert alert-{{ message.tags }}">{{ message }}</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<form method="post" class="needs-validation" novalidate>
|
||||
{% csrf_token %}
|
||||
{{ form.non_field_errors }}
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="id_name">主域名</label>
|
||||
{{ form.name }}
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="id_subdomains">接入子域名(逗号分隔)</label>
|
||||
{{ form.subdomains }}
|
||||
<div class="form-text">例如:www,static</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label" for="id_origin_host">源站地址</label>
|
||||
{{ form.origin_host }}
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<label class="form-label" for="id_origin_protocol">协议</label>
|
||||
{{ form.origin_protocol }}
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<label class="form-label" for="id_origin_port">端口</label>
|
||||
{{ form.origin_port }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label" for="id_plan">套餐</label>
|
||||
{{ form.plan }}
|
||||
</div>
|
||||
<div class="col-md-6 mb-3 form-check">
|
||||
{{ form.enable_websocket }}
|
||||
<label class="form-check-label">启用 WebSocket</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<button type="submit" class="btn btn-primary">创建</button>
|
||||
<a href="{% url 'domains:list' %}" class="btn btn-secondary">取消</a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="alert alert-info mt-4">
|
||||
创建成功后系统会自动跳转到域名详情页:
|
||||
在详情页可查看生成的 CNAME 目标,并可点击“检测 DNS 生效”按钮进行验证。
|
||||
如未设置套餐将默认使用系统配置的免费额度(可在运营面板调整)。
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
339
templates/domains/detail.html
Normal file
339
templates/domains/detail.html
Normal file
@@ -0,0 +1,339 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block title %}域名详情 - {{ domain.name }}{% endblock %}
|
||||
{% block extra_head %}
|
||||
<style>
|
||||
.sidebar-sticky { position: sticky; top: 80px; }
|
||||
.list-group .list-group-item.active { background-color: #0d6efd; border-color: #0d6efd; }
|
||||
.card .badge { font-size: 0.9rem; }
|
||||
.progress { height: 20px; }
|
||||
.progress-bar { font-size: 0.8rem; }
|
||||
@media (max-width: 991.98px){ .sidebar-sticky { position: static; } }
|
||||
.table td, .table th { vertical-align: middle; }
|
||||
.text-muted.small { line-height: 1.2; }
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row g-4">
|
||||
<div class="col-lg-2 col-md-3">
|
||||
<div class="sidebar-sticky">
|
||||
<div class="list-group">
|
||||
<a class="list-group-item list-group-item-action active" href="#">概览</a>
|
||||
<a class="list-group-item list-group-item-action" href="#">统计</a>
|
||||
<a class="list-group-item list-group-item-action" href="#">接入信息</a>
|
||||
<a class="list-group-item list-group-item-action" href="{% url 'domains:settings' domain_id=domain.id %}">设置</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-10 col-md-9">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h3>域名:{{ domain.name }}</h3>
|
||||
<div class="btn-group">
|
||||
<a href="{% url 'domains:list' %}" class="btn btn-outline-secondary">返回列表</a>
|
||||
<a href="{% url 'domains:upgrade' domain_id=domain.id %}" class="btn btn-primary" onclick="return confirm('升级套餐将影响后续计费,确认继续?');">升级套餐</a>
|
||||
<a href="{% url 'domains:logs' domain_id=domain.id %}" class="btn btn-outline-primary">访问日志</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">概览</div>
|
||||
<div class="card-body">
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<div>当前套餐:{% if domain.current_plan %}{{ domain.current_plan.name }}{% else %}-{% endif %}</div>
|
||||
<div>状态:{{ domain.get_status_display }}</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-1">本月流量:{{ used_gb }} GB / {{ total_quota_gb }} GB</div>
|
||||
<div class="progress" role="progressbar" aria-label="Monthly usage" aria-valuenow="{{ progress_pct }}" aria-valuemin="0" aria-valuemax="100">
|
||||
<div class="progress-bar" style="width: {{ progress_pct }}%">{{ progress_pct }}%</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">统计(近30天)</div>
|
||||
<div class="card-body">
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-4">
|
||||
<div class="fw-semibold">近 7 天合计</div>
|
||||
<div><span class="badge bg-primary">{{ last7_total_gb|default:0 }} GB</span></div>
|
||||
<div class="text-muted small">均值:{{ last7_avg_gb|default:0 }} GB/日</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="fw-semibold">近 30 天合计</div>
|
||||
<div><span class="badge bg-info text-dark">{{ last30_total_gb|default:0 }} GB</span></div>
|
||||
<div class="text-muted small">均值:{{ last30_avg_gb|default:0 }} GB/日</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="fw-semibold">峰值日(流量)</div>
|
||||
<div>
|
||||
{% if peak_volume_day %}
|
||||
<span class="badge bg-warning text-dark">{{ peak_volume_day.day }} / {{ peak_volume_day.gb }} GB</span>
|
||||
{% else %}
|
||||
<span class="text-muted">无数据</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-4">
|
||||
<div class="fw-semibold">峰值日(带宽)</div>
|
||||
<div>
|
||||
{% if peak_bandwidth_day %}
|
||||
<span class="badge bg-secondary">{{ peak_bandwidth_day.day }} / {{ peak_bandwidth_day.mbps }} Mbps</span>
|
||||
{% else %}
|
||||
<span class="text-muted">无数据</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<div class="fw-semibold">请求数 / 状态码分布</div>
|
||||
<div class="mb-2">
|
||||
{% if requests_24h_total %}
|
||||
近 24 小时请求数:<span class="badge bg-primary">{{ requests_24h_total }}</span>
|
||||
{% else %}
|
||||
<span class="text-muted">请求数不可用(请在设置启用访问日志)</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if status_bins %}
|
||||
<div class="d-flex gap-2 flex-wrap">
|
||||
<span class="badge text-bg-success">2xx {{ status_bins.2xx }}</span>
|
||||
<span class="badge text-bg-info">3xx {{ status_bins.3xx }}</span>
|
||||
<span class="badge text-bg-warning text-dark">4xx {{ status_bins.4xx }}</span>
|
||||
<span class="badge text-bg-danger">5xx {{ status_bins.5xx }}</span>
|
||||
</div>
|
||||
{% if status_top %}
|
||||
<div class="text-muted small mt-1">Top 状态码:
|
||||
{% for code,count in status_top %}
|
||||
<span class="me-2"><code>{{ code }}</code> {{ count }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div class="text-muted small">状态码分布不可用。</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% if traffic_rows %}
|
||||
<canvas id="trafficChart" height="120" class="mb-3" style="width: 100%;"></canvas>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>日期</th>
|
||||
<th>流量(GB)</th>
|
||||
<th>峰值带宽(Mbps)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for r in traffic_rows %}
|
||||
<tr>
|
||||
<td>{{ r.day }}</td>
|
||||
<td>{{ r.gb }}</td>
|
||||
<td>{% if r.peak_mbps %}{{ r.peak_mbps }}{% else %}<span class="text-muted">-</span>{% endif %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="text-muted">暂无统计数据。</div>
|
||||
{% endif %}
|
||||
<div class="small text-muted">说明:统计数据来源于 GoEdge 每日拉取结果,仅供参考。</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">接入信息(CNAME)</div>
|
||||
<div class="card-body">
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>主机名</th>
|
||||
<th>期望 CNAME 目标</th>
|
||||
<th>实际解析</th>
|
||||
<th>状态</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% if cname_results %}
|
||||
{% for r in cname_results %}
|
||||
<tr>
|
||||
<td>{{ r.hostname }}</td>
|
||||
<td><code>{{ r.expected }}</code></td>
|
||||
<td>
|
||||
{% if r.actual %}
|
||||
{% for a in r.actual %}<code>{{ a }}</code>{% if not forloop.last %}, {% endif %}{% endfor %}
|
||||
{% else %}
|
||||
<span class="text-muted">(无)</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if r.ok %}
|
||||
<span class="badge text-bg-success">正确</span>
|
||||
{% else %}
|
||||
<span class="badge text-bg-warning">未匹配</span>
|
||||
{% if r.error %}<div class="text-muted">{{ r.error }}</div>{% endif %}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% for host, target in domain.cname_targets.items %}
|
||||
<tr>
|
||||
<td>{{ host }}</td>
|
||||
<td><code>{{ target }}</code></td>
|
||||
<td class="text-muted">(未检测)</td>
|
||||
<td><span class="badge text-bg-secondary">未知</span></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<form method="post" action="{% url 'domains:check_dns' domain_id=domain.id %}" class="mt-2">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="btn btn-primary">立即检测 DNS 生效</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">GoEdge 同步状态</div>
|
||||
<div class="card-body">
|
||||
{% if goedge_status and not goedge_status.error %}
|
||||
<div class="row g-3">
|
||||
<div class="col-md-3">
|
||||
<div class="fw-semibold">Web ID</div>
|
||||
<div>{% if goedge_status.webId %}<span class="badge text-bg-secondary">{{ goedge_status.webId }}</span>{% else %}<span class="text-muted">未获取</span>{% endif %}</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="fw-semibold">访问日志</div>
|
||||
{% if goedge_status.accessLog.isOn %}
|
||||
<span class="badge text-bg-success">已启用</span>
|
||||
{% elif goedge_status.accessLog.isOn is not none %}
|
||||
<span class="badge text-bg-secondary">未启用</span>
|
||||
{% else %}
|
||||
<span class="text-muted">未知</span>
|
||||
{% endif %}
|
||||
{% if goedge_status.accessLog.policyId %}
|
||||
<div class="text-muted small">PolicyId: {{ goedge_status.accessLog.policyId }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="fw-semibold">WebSocket</div>
|
||||
{% if goedge_status.websocket.isOn %}
|
||||
<span class="badge text-bg-success">已启用</span>
|
||||
{% elif goedge_status.websocket.isOn is not none %}
|
||||
<span class="badge text-bg-secondary">未启用</span>
|
||||
{% else %}
|
||||
<span class="text-muted">未知</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="fw-semibold">WAF</div>
|
||||
{% if goedge_status.firewall.isOn %}
|
||||
<span class="badge text-bg-success">已启用</span>
|
||||
{% elif goedge_status.firewall.isOn is not none %}
|
||||
<span class="badge text-bg-secondary">未启用</span>
|
||||
{% else %}
|
||||
<span class="text-muted">未知</span>
|
||||
{% endif %}
|
||||
{% if goedge_status.firewall.policyId %}
|
||||
<div class="text-muted small">PolicyId: {{ goedge_status.firewall.policyId }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="fw-semibold">SSL策略</div>
|
||||
<div>{% if goedge_status.sslPolicy.id %}<span class="badge text-bg-secondary">{{ goedge_status.sslPolicy.id }}</span>{% else %}<span class="text-muted">未获取</span>{% endif %}</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="fw-semibold">HTTP/3</div>
|
||||
{% if goedge_status.sslPolicy.http3Enabled %}
|
||||
<span class="badge text-bg-success">已启用</span>
|
||||
{% elif goedge_status.sslPolicy.http3Enabled is not none %}
|
||||
<span class="badge text-bg-secondary">未启用</span>
|
||||
{% else %}
|
||||
<span class="text-muted">未知</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% elif goedge_status and goedge_status.error %}
|
||||
<div class="alert alert-warning">状态查询失败:{{ goedge_status.error }}</div>
|
||||
{% else %}
|
||||
<div class="text-muted">当前域名尚未关联 GoEdge 服务或无可查询信息。</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_scripts %}
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var rows = [
|
||||
{% for r in traffic_rows reversed %}
|
||||
{ day: '{{ r.day|date:"Y-m-d" }}', gb: {{ r.gb|default:0 }}, peak_mbps: {% if r.peak_mbps %}{{ r.peak_mbps }}{% else %}null{% endif %} },
|
||||
{% endfor %}
|
||||
];
|
||||
if (!rows.length || !window.Chart) return;
|
||||
var labels = rows.map(function(x){ return x.day; });
|
||||
var dataGb = rows.map(function(x){ return x.gb || 0; });
|
||||
var dataMbps = rows.map(function(x){ return x.peak_mbps === null ? null : x.peak_mbps; });
|
||||
var el = document.getElementById('trafficChart');
|
||||
if (!el) return;
|
||||
var ctx = el.getContext('2d');
|
||||
new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: labels,
|
||||
datasets: [
|
||||
{
|
||||
label: '流量(GB)',
|
||||
data: dataGb,
|
||||
borderColor: '#0d6efd',
|
||||
backgroundColor: 'rgba(13,110,253,0.15)',
|
||||
fill: true,
|
||||
tension: 0.3,
|
||||
yAxisID: 'yGb'
|
||||
},
|
||||
{
|
||||
label: '峰值带宽(Mbps)',
|
||||
data: dataMbps,
|
||||
borderColor: '#6f42c1',
|
||||
backgroundColor: 'rgba(111,66,193,0.08)',
|
||||
fill: false,
|
||||
tension: 0.3,
|
||||
yAxisID: 'yMbps'
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
interaction: { mode: 'index', intersect: false },
|
||||
plugins: {
|
||||
legend: { position: 'bottom' },
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
label: function(ctx){
|
||||
var suffix = ctx.dataset.yAxisID === 'yGb' ? ' GB' : ' Mbps';
|
||||
return ctx.dataset.label + ': ' + ctx.formattedValue + suffix;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
yGb: { type: 'linear', position: 'left', title: { display: true, text: 'GB' } },
|
||||
yMbps: { type: 'linear', position: 'right', title: { display: true, text: 'Mbps' }, grid: { drawOnChartArea: false } }
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
35
templates/domains/list.html
Normal file
35
templates/domains/list.html
Normal file
@@ -0,0 +1,35 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block title %}域名列表{% endblock %}
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h3>域名列表</h3>
|
||||
<a href="{% url 'domains:add' %}" class="btn btn-primary">添加域名</a>
|
||||
</div>
|
||||
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>域名</th>
|
||||
<th>套餐</th>
|
||||
<th>状态</th>
|
||||
<th>已用/本月</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for d in domains %}
|
||||
<tr>
|
||||
<td>{{ d.name }}</td>
|
||||
<td>{% if d.current_plan %}{{ d.current_plan.name }}{% else %}-{% endif %}</td>
|
||||
<td>{{ d.get_status_display }}</td>
|
||||
<td>{% if d.total_quota_gb %}{{ d.used_gb }} GB / {{ d.total_quota_gb }} GB{% else %}-{% endif %}</td>
|
||||
<td>
|
||||
<a href="{% url 'domains:detail' domain_id=d.id %}" class="btn btn-sm btn-outline-secondary">详情</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="5" class="text-center">暂无域名</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endblock %}
|
||||
86
templates/domains/logs.html
Normal file
86
templates/domains/logs.html
Normal file
@@ -0,0 +1,86 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block content %}
|
||||
<div class="container py-4">
|
||||
<h3>访问日志 - {{ domain.name }}</h3>
|
||||
<form class="row g-3 mb-3" method="get">
|
||||
<div class="col-md-2">
|
||||
<label class="form-label">日期(YYYYMMDD)</label>
|
||||
<input type="text" name="day" value="{{ day }}" class="form-control" placeholder="20251117">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label class="form-label">开始小时</label>
|
||||
<input type="text" name="hour_from" value="{{ hour_from }}" class="form-control" placeholder="00">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label class="form-label">结束小时</label>
|
||||
<input type="text" name="hour_to" value="{{ hour_to }}" class="form-control" placeholder="23">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label class="form-label">IP</label>
|
||||
<input type="text" name="ip" value="{{ ip }}" class="form-control" placeholder="1.2.3.4">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">关键词</label>
|
||||
<input type="text" name="keyword" value="{{ keyword }}" class="form-control" placeholder="path or UA">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label class="form-label">状态码</label>
|
||||
<input type="text" name="status_code" value="{{ status_code }}" class="form-control" placeholder="200">
|
||||
</div>
|
||||
<div class="col-md-1">
|
||||
<label class="form-label">条数</label>
|
||||
<input type="number" name="size" value="{{ size }}" class="form-control" min="1" max="200">
|
||||
</div>
|
||||
<div class="col-md-2 d-flex align-items-end">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" name="reverse" value="1" {% if reverse %}checked{% endif %} id="revCheck">
|
||||
<label class="form-check-label" for="revCheck">反向</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-10 d-flex align-items-end justify-content-end gap-2">
|
||||
<button type="submit" class="btn btn-primary">查询</button>
|
||||
<a class="btn btn-outline-secondary" href="?day={{ day }}&hour_from={{ hour_from }}&hour_to={{ hour_to }}&ip={{ ip }}&keyword={{ keyword }}&status_code={{ status_code }}&size={{ size }}&reverse={{ reverse|yesno:'1,0' }}&export=csv">导出 CSV</a>
|
||||
<a class="btn btn-outline-secondary" href="?day={{ day }}&hour_from={{ hour_from }}&hour_to={{ hour_to }}&ip={{ ip }}&keyword={{ keyword }}&status_code={{ status_code }}&size={{ size }}&reverse={{ reverse|yesno:'1,0' }}&export=json">导出 JSON</a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>时间</th>
|
||||
<th>域名</th>
|
||||
<th>IP</th>
|
||||
<th>方法</th>
|
||||
<th>路径</th>
|
||||
<th>状态码</th>
|
||||
<th>字节数</th>
|
||||
<th>UA</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for log in logs %}
|
||||
<tr>
|
||||
<td>{{ log.timeLocal }}</td>
|
||||
<td>{{ log.host }}</td>
|
||||
<td>{{ log.remoteAddr }}</td>
|
||||
<td>{{ log.method }}</td>
|
||||
<td>{{ log.requestURI }}</td>
|
||||
<td>{{ log.status }}</td>
|
||||
<td>{{ log.bytesSent }}</td>
|
||||
<td>{{ log.userAgent }}</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="8" class="text-center">无数据</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{% if has_more and request_id %}
|
||||
<div class="d-flex justify-content-end">
|
||||
<a class="btn btn-outline-secondary" href="?day={{ day }}&hour_from={{ hour_from }}&hour_to={{ hour_to }}&ip={{ ip }}&keyword={{ keyword }}&status_code={{ status_code }}&size={{ size }}&reverse={{ reverse|yesno:'1,0' }}&request_id={{ request_id }}">下一页</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
185
templates/domains/settings.html
Normal file
185
templates/domains/settings.html
Normal file
@@ -0,0 +1,185 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block title %}功能设置 - {{ domain.name }}{% endblock %}
|
||||
{% block extra_head %}
|
||||
<style>
|
||||
.form-text { font-size: 0.85rem; }
|
||||
.sidebar-sticky { position: sticky; top: 80px; }
|
||||
@media (max-width: 991.98px){ .sidebar-sticky { position: static; } }
|
||||
textarea.json-field { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; }
|
||||
.disabled-hint { color: #6c757d; font-size: 0.85rem; }
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row g-4">
|
||||
<div class="col-lg-2 col-md-3">
|
||||
<div class="sidebar-sticky">
|
||||
<div class="list-group">
|
||||
<a class="list-group-item list-group-item-action" href="{% url 'domains:detail' domain_id=domain.id %}">概览</a>
|
||||
<a class="list-group-item list-group-item-action" href="{% url 'domains:detail' domain_id=domain.id %}#stats">统计</a>
|
||||
<a class="list-group-item list-group-item-action" href="{% url 'domains:detail' domain_id=domain.id %}#cname">接入信息</a>
|
||||
<a class="list-group-item list-group-item-action active" href="#">设置</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-10 col-md-9">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h3>功能设置:{{ domain.name }}</h3>
|
||||
<div class="btn-group">
|
||||
<a href="{% url 'domains:detail' domain_id=domain.id %}" class="btn btn-outline-secondary">返回详情</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-info">
|
||||
根据当前套餐的功能开关,部分设置可能不可用或被禁用。变更套餐可启用更多功能。
|
||||
</div>
|
||||
|
||||
<form method="post" action="{% url 'domains:settings' domain_id=domain.id %}">
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">安全与网络</div>
|
||||
<div class="card-body">
|
||||
<div class="form-check form-switch mb-2">
|
||||
<input type="checkbox" class="form-check-input" id="id_waf_enabled" name="waf_enabled" {% if form.waf_enabled.value %}checked{% endif %} {% if not plan_features.waf_enabled %}disabled{% endif %}>
|
||||
<label class="form-check-label" for="id_waf_enabled">启用 WAF</label>
|
||||
{% if not plan_features.waf_enabled %}<div class="disabled-hint">当前套餐不支持 WAF。</div>{% endif %}
|
||||
</div>
|
||||
<div class="form-check form-switch mb-2">
|
||||
<input type="checkbox" class="form-check-input" id="id_http3_enabled" name="http3_enabled" {% if form.http3_enabled.value %}checked{% endif %} {% if not plan_features.http3_enabled %}disabled{% endif %}>
|
||||
<label class="form-check-label" for="id_http3_enabled">启用 HTTP/3</label>
|
||||
{% if not plan_features.http3_enabled %}<div class="disabled-hint">当前套餐不支持 HTTP/3。</div>{% endif %}
|
||||
</div>
|
||||
<div class="form-check form-switch mb-2">
|
||||
<input type="checkbox" class="form-check-input" id="id_websocket_enabled" name="websocket_enabled" {% if form.websocket_enabled.value %}checked{% endif %} {% if not plan_features.websocket_enabled %}disabled{% endif %}>
|
||||
<label class="form-check-label" for="id_websocket_enabled">启用 WebSocket</label>
|
||||
{% if not plan_features.websocket_enabled %}<div class="disabled-hint">当前套餐不支持 WebSocket。</div>{% endif %}
|
||||
</div>
|
||||
<div class="form-check form-switch mb-2">
|
||||
<input type="checkbox" class="form-check-input" id="id_logs_enabled" name="logs_enabled" {% if form.logs_enabled.value %}checked{% endif %} {% if not plan_features.logs_enabled %}disabled{% endif %}>
|
||||
<label class="form-check-label" for="id_logs_enabled">启用日志下载</label>
|
||||
{% if not plan_features.logs_enabled %}<div class="disabled-hint">当前套餐不支持实时/离线日志。</div>{% endif %}
|
||||
</div>
|
||||
<div class="form-check form-switch mb-2">
|
||||
<input type="checkbox" class="form-check-input" id="id_redirect_https_enabled" name="redirect_https_enabled" {% if form.redirect_https_enabled.value %}checked{% endif %}>
|
||||
<label class="form-check-label" for="id_redirect_https_enabled">强制 HTTP→HTTPS 跳转</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">缓存与页面规则</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-2">
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="applyCacheTemplate('static')">套用静态资源缓存</button>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="applyPageTemplate('redirect_https')">套用强制 HTTPS 跳转</button>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="applyPageTemplate('rewrite_www')">套用 www→非www 重写</button>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="downloadJson('id_cache_rules_json')">导出缓存 JSON</button>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="downloadJson('id_page_rules_json')">导出页面规则 JSON</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="id_cache_rules_json" class="form-label">缓存规则(JSON)</label>
|
||||
<textarea class="form-control json-field" id="id_cache_rules_json" name="cache_rules_json" rows="6">{{ form.cache_rules_json.value }}</textarea>
|
||||
<div class="form-text">示例:{"rules": [{"path": "/images/*", "ttl": 3600}]}</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="id_page_rules_json" class="form-label">页面规则(JSON)</label>
|
||||
<textarea class="form-control json-field" id="id_page_rules_json" name="page_rules_json" rows="6">{{ form.page_rules_json.value }}</textarea>
|
||||
<div class="form-text">示例:{"rules": [{"match": "example.com/*", "action": "cache_bypass"}]}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">IP 控制</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<label for="id_ip_whitelist" class="form-label">IP 白名单(逗号分隔)</label>
|
||||
<input type="text" class="form-control" id="id_ip_whitelist" name="ip_whitelist" value="{{ form.ip_whitelist.value }}">
|
||||
<div class="form-text">示例:1.2.3.4, 10.0.0.1</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="id_ip_blacklist" class="form-label">IP 黑名单(逗号分隔)</label>
|
||||
<input type="text" class="form-control" id="id_ip_blacklist" name="ip_blacklist" value="{{ form.ip_blacklist.value }}">
|
||||
<div class="form-text">示例:203.0.113.0/24</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex gap-2">
|
||||
<button type="submit" class="btn btn-primary" onclick="return confirm('保存设置会立即影响后端配置,确认提交?');">保存设置</button>
|
||||
<a href="{% url 'domains:detail' domain_id=domain.id %}" class="btn btn-outline-secondary">取消</a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="card mt-4">
|
||||
<div class="card-header">套餐功能可用性</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item">WAF:{% if plan_features.waf_enabled %}<span class="text-success">可用</span>{% else %}<span class="text-muted">不可用</span>{% endif %}</li>
|
||||
<li class="list-group-item">HTTP/3:{% if plan_features.http3_enabled %}<span class="text-success">可用</span>{% else %}<span class="text-muted">不可用</span>{% endif %}</li>
|
||||
<li class="list-group-item">WebSocket:{% if plan_features.websocket_enabled %}<span class="text-success">可用</span>{% else %}<span class="text-muted">不可用</span>{% endif %}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item">日志下载:{% if plan_features.logs_enabled %}<span class="text-success">可用</span>{% else %}<span class="text-muted">不可用</span>{% endif %}</li>
|
||||
<li class="list-group-item">自定义规则:{% if plan_features.page_rules_limit %}<span class="text-success">上限 {{ plan_features.page_rules_limit }}</span>{% else %}<span class="text-muted">不可用</span>{% endif %}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="small text-muted mt-2">提示:套餐功能由管理员在“套餐管理”中配置。</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block extra_scripts %}
|
||||
<script>
|
||||
function applyCacheTemplate(name){
|
||||
var el = document.getElementById('id_cache_rules_json');
|
||||
if(!el) return;
|
||||
var tpl = {};
|
||||
if(name === 'static'){
|
||||
tpl = {
|
||||
rules: [
|
||||
{ path: '/static/*', ttl: 3600 },
|
||||
{ path: '/images/*', ttl: 3600 },
|
||||
{ path: '/css/*', ttl: 1800 },
|
||||
{ path: '/js/*', ttl: 1800 }
|
||||
]
|
||||
};
|
||||
}
|
||||
el.value = JSON.stringify(tpl, null, 2);
|
||||
}
|
||||
function applyPageTemplate(name){
|
||||
var el = document.getElementById('id_page_rules_json');
|
||||
if(!el) return;
|
||||
var tpl = {};
|
||||
if(name === 'redirect_https'){
|
||||
tpl = { redirectToHTTPS: { isOn: true } };
|
||||
var chk = document.getElementById('id_redirect_https_enabled');
|
||||
if(chk) chk.checked = true;
|
||||
}
|
||||
if(name === 'rewrite_www'){
|
||||
tpl = { rewriteRules: [{ pattern: '^www\\.(.*)$', replace: '$1', flags: 'R=301' }] };
|
||||
}
|
||||
el.value = JSON.stringify(tpl, null, 2);
|
||||
}
|
||||
function downloadJson(textareaId){
|
||||
var el = document.getElementById(textareaId);
|
||||
if(!el) return;
|
||||
var blob = new Blob([el.value || '{}'], {type: 'application/json'});
|
||||
var a = document.createElement('a');
|
||||
a.href = URL.createObjectURL(blob);
|
||||
a.download = textareaId + '.json';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
78
templates/domains/upgrade.html
Normal file
78
templates/domains/upgrade.html
Normal file
@@ -0,0 +1,78 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>升级套餐 - {{ domain.name }}</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark mb-4">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="#">PyGoEdge 面板</a>
|
||||
<div class="d-flex gap-2">
|
||||
<a class="btn btn-outline-light" href="/domains/">域名</a>
|
||||
<a class="btn btn-outline-light" href="/plans/">套餐</a>
|
||||
<a class="btn btn-outline-light" href="/admin/">Admin</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="container" style="max-width: 860px;">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h3>升级套餐:{{ domain.name }}</h3>
|
||||
<a href="{% url 'domains:detail' domain_id=domain.id %}" class="btn btn-outline-secondary">返回详情</a>
|
||||
</div>
|
||||
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
<div class="alert alert-{{ message.tags }}">{{ message }}</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<form method="post" onsubmit="return confirm('确认将该域名升级到所选套餐?');">
|
||||
{% csrf_token %}
|
||||
<div class="alert alert-warning">提示:功能开关将立即按新套餐生效;计费按默认规则在下一个周期计算(当前周期不追溯调整)。</div>
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>选择</th>
|
||||
<th>名称</th>
|
||||
<th>月费(/域名)</th>
|
||||
<th>包含流量(GB)</th>
|
||||
<th>超量单价(元/GB)</th>
|
||||
<th>功能</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for p in plans %}
|
||||
<tr>
|
||||
<td>
|
||||
<input type="radio" name="plan_id" value="{{ p.id }}" required {% if domain.current_plan and domain.current_plan.id == p.id %}disabled{% endif %} />
|
||||
</td>
|
||||
<td>{{ p.name }}</td>
|
||||
<td>{{ p.base_price_per_domain }}</td>
|
||||
<td>{{ p.included_traffic_gb_per_domain }}</td>
|
||||
<td>{{ p.overage_price_per_gb }}</td>
|
||||
<td>
|
||||
{% if p.features %}
|
||||
<span class="badge bg-secondary me-1">WAF: {% if p.features.waf_enabled %}✔{% else %}✖{% endif %}</span>
|
||||
<span class="badge bg-secondary me-1">HTTP/3: {% if p.features.http3_enabled %}✔{% else %}✖{% endif %}</span>
|
||||
<span class="badge bg-secondary me-1">日志: {% if p.features.logs_enabled %}✔{% else %}✖{% endif %}</span>
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="6" class="text-center">暂无可升级的公开套餐</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<button type="submit" class="btn btn-primary">确认升级</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
40
templates/plans/list.html
Normal file
40
templates/plans/list.html
Normal file
@@ -0,0 +1,40 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block title %}套餐列表{% endblock %}
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h3>公开套餐</h3>
|
||||
</div>
|
||||
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>名称</th>
|
||||
<th>月费(/域名)</th>
|
||||
<th>包含流量(GB)</th>
|
||||
<th>超量单价(元/GB)</th>
|
||||
<th>功能</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for p in plans %}
|
||||
<tr>
|
||||
<td>{{ p.name }}</td>
|
||||
<td>{{ p.base_price_per_domain }}</td>
|
||||
<td>{{ p.included_traffic_gb_per_domain }}</td>
|
||||
<td>{{ p.overage_price_per_gb }}</td>
|
||||
<td>
|
||||
{% if p.features %}
|
||||
<span class="badge bg-secondary me-1">WAF: {% if p.features.waf_enabled %}✔{% else %}✖{% endif %}</span>
|
||||
<span class="badge bg-secondary me-1">HTTP/3: {% if p.features.http3_enabled %}✔{% else %}✖{% endif %}</span>
|
||||
<span class="badge bg-secondary me-1">日志: {% if p.features.logs_enabled %}✔{% else %}✖{% endif %}</span>
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="5" class="text-center">暂无公开套餐,请在后台新增</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user