TenantDrive/templates/admin.html

649 lines
30 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>网盘租户系统 - 管理员面板</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.7.2/font/bootstrap-icons.css" rel="stylesheet">
<style>
.sidebar {
position: fixed;
top: 0;
bottom: 0;
left: 0;
z-index: 100;
padding: 20px;
background-color: #f8f9fa;
}
.main-content {
margin-left: 250px;
padding: 20px;
}
.nav-link {
color: #333;
padding: 10px 15px;
border-radius: 5px;
margin-bottom: 5px;
}
.nav-link:hover {
background-color: #e9ecef;
}
.nav-link.active {
background-color: #0d6efd;
color: white;
}
</style>
</head>
<body>
<div class="container-fluid">
<div class="row">
<!-- 侧边栏 -->
<nav class="col-md-3 col-lg-2 d-md-block sidebar">
<div class="position-sticky">
<h3 class="mb-4">管理员面板</h3>
<ul class="nav flex-column">
<li class="nav-item">
<a class="nav-link active" href="#dashboard">
<i class="bi bi-speedometer2 me-2"></i>仪表盘
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#accounts">
<i class="bi bi-cloud me-2"></i>网盘账号管理
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#links">
<i class="bi bi-link-45deg me-2"></i>外链管理
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#statistics">
<i class="bi bi-graph-up me-2"></i>统计分析
</a>
</li>
</ul>
</div>
</nav>
<!-- 主要内容区域 -->
<main class="col-md-9 ms-sm-auto col-lg-10 px-md-4 main-content">
<!-- 仪表盘 -->
<div id="dashboard" class="tab-content">
<h2 class="mb-4">系统概览</h2>
<div class="row">
<div class="col-md-4">
<div class="card">
<div class="card-body">
<h5 class="card-title">网盘账号总数</h5>
<p class="card-text display-4">0</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-body">
<h5 class="card-title">活跃外链数</h5>
<p class="card-text display-4">0</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-body">
<h5 class="card-title">今日访问量</h5>
<p class="card-text display-4">0</p>
</div>
</div>
</div>
</div>
</div>
<!-- 网盘账号管理 -->
<div id="accounts" class="tab-content d-none">
<h2 class="mb-4">网盘账号管理</h2>
<!--
添加网盘账号按钮
btn: Bootstrap按钮基础类
btn-primary: 蓝色主题按钮样式
mb-3: margin-bottom为3个单位提供底部间距
data-bs-toggle="modal": 指示此按钮将触发模态框
data-bs-target="#addAccountModal": 指定要打开的模态框ID
-->
<button class="btn btn-primary mb-3" data-bs-toggle="modal" data-bs-target="#addAccountModal">
<!--
bi bi-plus-circle: Bootstrap图标库中的加号图标
me-2: margin-end为2个单位在图标右侧提供间距
-->
<i class="bi bi-plus-circle me-2"></i>添加网盘账号
</button>
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th>网盘类型</th>
<th>账号名称</th>
<th>状态</th>
<th>剩余容量</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<!-- 账号列表将通过JavaScript动态填充 -->
<tr>
{% for user_drive in alluser_drives %}
<td>{{user_drive.provider_name}}</td>
<td>{{user_drive.provider_name}}</td>
<td><span class="badge bg-success">正常</span></td>
<td>1.5TB / 2TB</td>
<td>
<button class="btn btn-sm btn-info" id="editudrivebtn" data-bs-toggle="modal" data-bs-target="#editudriveModal" tid="{{ user_drive.id }}"><i class="bi bi-pencil"></i></button>
<button class="btn btn-sm btn-danger"><i class="bi bi-trash"></i></button>
</td>
{% endfor %}
</tr>
</tbody>
</table>
</div>
</div>
<!-- 外链管理 -->
<div id="links" class="tab-content d-none">
<h2 class="mb-4">外链管理</h2>
<!-- 添加外链按钮 -->
<button class="btn btn-primary mb-3" data-bs-toggle="modal" data-bs-target="#addExlinkModal">
<i class="bi bi-plus-circle me-2"></i>添加外链
</button>
<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>
</tr>
</thead>
<tbody>
<!-- 外链列表将通过JavaScript动态填充 -->
</tbody>
</table>
</div>
</div>
<!-- 统计分析 -->
<div id="statistics" class="tab-content d-none">
<h2 class="mb-4">统计分析</h2>
<div class="row">
<div class="col-md-6">
<div class="card">
<div class="card-body">
<h5 class="card-title">外链访问趋势</h5>
<canvas id="accessChart"></canvas>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card">
<div class="card-body">
<h5 class="card-title">网盘使用分布</h5>
<canvas id="storageChart"></canvas>
</div>
</div>
</div>
</div>
</div>
</main>
</div>
</div>
<!-- 添加网盘账号模态框 -->
<div class="modal fade" id="addAccountModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">添加网盘账号</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="addAccountForm">
<div class="mb-3">
<label class="form-label">网盘类型</label>
<select class="form-select" name="type" id="driveType" required>
<option value="">请选择网盘类型</option>
{% set provider_options = providers %}
{% for provider in provider_options %}
<option value="{{ provider.provider_name }}" data-needs-api="{{ provider.provider_name }}">{{ provider.provider_name}}</option>
{% endfor %}
</select>
</div>
<div class="mb-3">
<label class="form-label">配置信息 (JSON)</label>
<div class="position-relative">
<div id="jsonEditor" class="border rounded" style="height: 200px; font-family: monospace;"></div>
<textarea class="form-control d-none" name="config_json" id="jsonConfig"></textarea>
<div class="position-absolute top-0 end-0 p-2">
<button class="btn btn-sm btn-outline-secondary" type="button" id="formatJsonBtn">
<i class="bi bi-code-square"></i> 格式化
</button>
</div>
</div>
<div class="d-flex justify-content-between mt-1">
<div class="form-text">请输入有效的JSON格式配置信息</div>
<div class="form-text text-end"><span id="jsonLineCount">0</span> 行 | <span id="jsonCharCount">0</span> 字符</div>
</div>
<div id="jsonError" class="invalid-feedback"></div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary" id="saveAccount">保存</button>
</div>
</div>
</div>
</div>
<!-- 编辑网盘账号模态框 -->
<div class="modal fade" id="editudriveModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">编辑网盘驱动</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="editudriveForm">
<div class="mb-3">
<label class="form-label">配置信息 (JSON)</label>
<div class="position-relative">
<div id="jsonEditor1" class="border rounded" style="height: 200px; font-family: monospace;"></div>
<textarea class="form-control d-none" name="config_json" id="jsonConfig1"></textarea>
<div class="position-absolute top-0 end-0 p-2">
<button class="btn btn-sm btn-outline-secondary" type="button" id="formatJsonBtn">
<i class="bi bi-code-square"></i> 格式化
</button>
</div>
</div>
<div class="d-flex justify-content-between mt-1">
<div class="form-text">请输入有效的JSON格式配置信息</div>
<div class="form-text text-end"><span id="jsonLineCount1">0</span> 行 | <span id="jsonCharCount1">0</span> 字符</div>
</div>
<div id="jsonError" class="invalid-feedback"></div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary" id="saveAccount">保存</button>
</div>
</div>
</div>
</div>
<!-- 添加网盘外链模态框 -->
<div class="modal fade" id="addExlinkModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">添加网盘外链</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="addExlinkForm">
<div class="mb-3">
<label class="form-label">网盘类型</label>
<select class="form-select" name="disk_type" required>
<option value="">请选择网盘类型</option>
<option value="aliyun">阿里云盘</option>
<option value="baidu">百度网盘</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">账号</label>
<select class="form-select" name="account_id" required>
<option value="">请选择账号</option>
<!-- 这里应该通过JavaScript动态填充账号列表 -->
<option value="aliyun">阿里云盘</option>
<option value="baidu">百度网盘</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">剩余次数</label>
<input type="number" class="form-control" name="remaining_count" required>
</div>
<div class="mb-3">
<label class="form-label">有效期</label>
<input type="date" class="form-control" name="expiry_date" required>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary" id="saveExlink">保存</button>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.12/ace.js" integrity="sha512-GZ1RIgZaSc8rnco/8CXfRdCpDxRCphenIiZ2ztLy3XQfCbQUSCuk8IudvNHxkRA3oUg6q0qejgN/qqyG1duv5Q==" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.12/ext-language_tools.min.js"></script>
<script>
// 导航切换
document.querySelectorAll('.nav-link').forEach(link => {
link.addEventListener('click', function(e) {
e.preventDefault();
document.querySelectorAll('.nav-link').forEach(l => l.classList.remove('active'));
this.classList.add('active');
const targetId = this.getAttribute('href').substring(1);
document.querySelectorAll('.tab-content').forEach(content => {
content.classList.add('d-none');
});
document.getElementById(targetId).classList.remove('d-none');
});
});
// Bootstrap消息提示函数
function showMessage(message, type = 'success', duration = 3000) {
// 创建消息元素
const messageDiv = document.createElement('div');
// 设置样式类
messageDiv.classList.add('position-fixed', 'top-0', 'start-50', 'translate-middle-x', 'mt-3', 'p-3', 'rounded', 'shadow');
// 根据类型设置不同的样式
switch(type) {
case 'success':
messageDiv.classList.add('bg-success', 'text-white');
break;
case 'warning':
messageDiv.classList.add('bg-warning', 'text-dark');
break;
case 'error':
messageDiv.classList.add('bg-danger', 'text-white');
break;
case 'info':
messageDiv.classList.add('bg-info', 'text-white');
break;
default:
messageDiv.classList.add('bg-success', 'text-white');
}
// 设置消息内容
messageDiv.textContent = message;
// 设置z-index确保显示在最上层
messageDiv.style.zIndex = '9999';
// 添加到body
document.body.appendChild(messageDiv);
// 设置淡入效果
messageDiv.style.opacity = '0';
messageDiv.style.transition = 'opacity 0.3s ease-in-out';
setTimeout(() => {
messageDiv.style.opacity = '1';
}, 10);
// 设置定时移除
setTimeout(() => {
messageDiv.style.opacity = '0';
setTimeout(() => {
document.body.removeChild(messageDiv);
}, 300);
}, duration);
}
// 使用示例:
// showMessage('操作成功', 'success');
// showMessage('警告信息', 'warning');
// showMessage('错误信息', 'error');
// showMessage('提示信息', 'info');
//添加保存外链事件
$('#saveExlink').on('click', function() {
// 获取表单数据
const formData = new FormData(document.querySelector('#addExlinkModal form'));
const formValues = {
disk_type: formData.get('disk_type'),
account_id: formData.get('account_id'),
remaining_count: formData.get('remaining_count'),
expiry_date: formData.get('expiry_date')
};
// 检查表单是否填写完整
if (!formValues.disk_type || !formValues.remaining_count || !formValues.account_id || !formValues.expiry_date) {
showMessage('请填写所有必填字段!', 'error');
return; // 停止提交
}
// 表单数据准备发送
const submitData = {
account_id: formValues.account_id,
remaining_count: formValues.remaining_count,
access_token: formValues.access_token
};
console.log('准备提交的数据:', submitData);
// 发送数据到服务器
$.ajax({
url: '/admin/exlink',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify({
data: formValues
}),
success: function(data) {
if (data.status) {
showMessage('账号保存成功',"success");
// 清空表单
$('#accountName').val('');
$('#accountToken').val('');
} else {
showMessage('账号保存失败: ' + data.message,"warning");
}
},
error: function(error) {
console.error('保存账号出错:', error);
showMessage('保存账号时发生错误,请查看控制台',"error");
}
});
});
// 初始化图表
const accessCtx = document.getElementById('accessChart').getContext('2d');
new Chart(accessCtx, {
type: 'line',
data: {
labels: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
datasets: [{
label: '访问量',
data: [0, 0, 0, 0, 0, 0, 0],
borderColor: 'rgb(75, 192, 192)',
tension: 0.1
}]
}
});
const storageCtx = document.getElementById('storageChart').getContext('2d');
new Chart(storageCtx, {
type: 'doughnut',
data: {
labels: ['阿里云盘', '百度网盘'],
datasets: [{
data: [0, 0],
backgroundColor: ['#ff6384', '#36a2eb']
}]
}
});
// 添加Ace编辑器的CDN引用
function loadAceEditor() {
if (typeof ace === 'undefined') {
// 如果ace未定义直接初始化编辑器
initAceEditor();
} else {
initAceEditor();
}
}
// json编辑器处理
function initAceEditor() {
// 初始化编辑器
const editor = ace.edit("jsonEditor");
const editor1 = ace.edit("jsonEditor1");
// 配置两个编辑器
const configureEditor = function(editor, configId, lineCountId, charCountId) {
editor.setTheme("ace/theme/monokai");
editor.session.setMode("ace/mode/json");
editor.setOptions({
fontSize: "12pt",
showPrintMargin: false,
enableBasicAutocompletion: true,
enableLiveAutocompletion: true
});
// 同步到隐藏的textarea
editor.getSession().on('change', function() {
document.getElementById(configId).value = editor.getSession().getValue();
document.getElementById(lineCountId).textContent = editor.getSession().getLength();
document.getElementById(charCountId).textContent = editor.getSession().getValue().length;
});
};
// 应用配置到两个编辑器
configureEditor(editor, 'jsonConfig', 'jsonLineCount', 'jsonCharCount');
configureEditor(editor1, 'jsonConfig1', 'jsonLineCount1', 'jsonCharCount1');
// 格式化按钮
document.getElementById('formatJsonBtn').addEventListener('click', function() {
const activeEditor = document.activeElement.closest('#jsonEditor') ? editor :
document.activeElement.closest('#jsonEditor1') ? editor1 : editor;
const editorElement = activeEditor === editor ? 'jsonEditor' : 'jsonEditor1';
try {
const json = JSON.parse(activeEditor.getSession().getValue());
activeEditor.setValue(JSON.stringify(json, null, 2), -1);
document.getElementById('jsonError').textContent = '';
document.getElementById(editorElement).classList.remove('border-danger');
} catch (e) {
document.getElementById('jsonError').textContent = '无效的JSON格式: ' + e.message;
document.getElementById('jsonError').style.display = 'block';
document.getElementById(editorElement).classList.add('border-danger');
}
});
}
document.addEventListener('DOMContentLoaded', function() {
loadAceEditor();
});
// 设置默认JSON模板
document.getElementById('driveType').addEventListener('change', function() {
const editor = ace.edit("jsonEditor");
const selectedType = this.value;
if (selectedType) {
let defaultJson = {};
// 获取所有网盘提供商数据
const providers = JSON.parse('{{ providers | tojson }}');
// 查找选中的网盘提供商
const selectedProvider = providers.find(provider => provider.provider_name === selectedType);
if (selectedProvider && selectedProvider.config_vars) {
// 如果找到对应的提供商配置,直接使用
defaultJson = selectedProvider.config_vars;
} else {
// 如果没有找到对应配置,使用默认模板
defaultJson = {
"provider_name": selectedType,
"config": {
"api_key": "",
"secret_key": "",
"redirect_uri": ""
},
"auth": {
"token": "",
"expires_in": 0
}
};
}
// 设置编辑器内容
editor.setValue(JSON.stringify(defaultJson, null, 2), -1);
}
});
// 设置编辑用户驱动JSON模板
document.getElementById('editudrivebtn').addEventListener('click', function() {
const editor = ace.edit("jsonEditor1");
const selectedtid = this.getAttribute('tid');
if (selectedtid) {
let defaultJson = {};
// 获取所有网盘提供商数据
const user_drives = JSON.parse('{{ alluser_drives | tojson }}');
// 查找选中的网盘提供商
console.log(typeof(selectedtid));
const selectedUdrive = user_drives.find(user_drive => user_drive.id == selectedtid);
console.log(selectedtid,selectedUdrive)
if (selectedUdrive && selectedUdrive.login_config) {
// 如果找到对应的提供商配置,直接使用
defaultJson = selectedUdrive.login_config;
} else {
// 如果没有找到对应配置,使用默认模板
defaultJson = {
"provider_name": selectedtid,
"config": {
"api_key": "",
"secret_key": "",
"redirect_uri": ""
},
"auth": {
"token": "",
"expires_in": 0
}
};
}
// 设置编辑器内容
editor.setValue(JSON.stringify(defaultJson, null, 2), -1);
}
});
// 初始化时设置一个空的JSON结构
window.addEventListener('load', function() {
const editor = ace.edit("jsonEditor");
const editor1 = ace.edit("jsonEditor1");
const emptyJson = {
"provider_name": "",
"config": {},
"auth": {}
};
const jsonString = JSON.stringify(emptyJson, null, 2);
// 为两个编辑器设置相同的初始值
editor.setValue(jsonString, -1);
editor1.setValue(jsonString, -1);
document.getElementById('jsonConfig').value = jsonString;
document.getElementById('jsonConfig1').value = jsonString;
});
</script>
</body>
</html>