This commit is contained in:
hashbro
2026-08-08 05:09:01 +08:00
parent 03a40105fc
commit 74a67c3900
523 changed files with 669 additions and 5965 deletions
@@ -0,0 +1,190 @@
@extends('admin.content')
@section('title', '钱包地址')
@section('content')
@php $portal = $portal ?? 'admin'; @endphp
<style>
.addr-chain-tag {
display: inline-block;
padding: 0 8px;
line-height: 20px;
border-radius: 2px;
font-size: 12px;
color: #fff;
}
.addr-chain-eth, .addr-chain-ethereum, .addr-chain-evm { background: #627eea; }
.addr-chain-btc, .addr-chain-bitcoin { background: #f7931a; }
.addr-chain-trx, .addr-chain-tron { background: #ff0013; }
.addr-chain-bnb, .addr-chain-bsc, .addr-chain-binance { background: #f3ba2f; color: #333; }
.addr-chain-sol, .addr-chain-solana { background: #9945ff; }
.addr-chain-ton { background: #0098ea; }
.addr-chain-unknown, .addr-chain-unkno { background: #909399; }
.addr-monitor-on { color: #16b777; font-weight: 600; }
.addr-monitor-off { color: #ff5722; font-weight: 600; }
.addr-cell { word-break: break-all; font-family: Menlo, Monaco, Consolas, monospace; font-size: 12px; }
</style>
<div class="layui-card">
<form class="layui-form layui-card-header layuiadmin-card-header-auto" lay-filter="LAY-addr-search">
<div class="layui-form-item">
<div class="layui-inline">
<label class="layui-form-label">渠道 ID</label>
<div class="layui-input-block">
<input type="text" name="channel_id" class="layui-input" autocomplete="off">
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">设备 ID</label>
<div class="layui-input-block">
<input type="text" name="device_key" class="layui-input" autocomplete="off">
</div>
</div>
@if ($portal === 'admin')
<div class="layui-inline">
<label class="layui-form-label">代理用户</label>
<div class="layui-input-block">
<select name="agent_user_id">
<option value="">全部</option>
@foreach ($agents as $a)
<option value="{{ $a->id }}">{{ $a->username }}</option>
@endforeach
</select>
</div>
</div>
@endif
<div class="layui-inline">
<label class="layui-form-label">来源</label>
<div class="layui-input-block">
<select name="source">
<option value="">全部</option>
@foreach ($sources as $s)
<option value="{{ $s }}">{{ $s }}</option>
@endforeach
</select>
</div>
</div>
<div class="layui-inline">
<button class="layui-btn" lay-submit lay-filter="LAY-addr-search">搜索</button>
</div>
</div>
</form>
<div class="layui-card-body">
<table id="LAY-addr-list" lay-filter="LAY-addr-list"></table>
<script type="text/html" id="LAY-addr-ops">
<a class="layui-btn layui-btn-warm layui-btn-xs" lay-event="edit">编辑</a>
<a class="layui-btn layui-btn-normal layui-btn-xs" lay-event="detail">设备详情</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 updateBase = @json(url('/'.$portal.'/addresses'));
function chainTag(chain) {
var raw = String(chain || 'UNKNOWN');
var key = raw.toLowerCase().replace(/[^a-z0-9]+/g, '');
var cls = 'addr-chain-tag addr-chain-' + (key || 'unknown');
return '<span class="' + cls + '">' + raw + '</span>';
}
table.render({
elem: '#LAY-addr-list',
id: 'LAY-addr-list',
url: @json(route($portal.'.addresses.data')),
cols: [[
{ field: 'id', title: 'ID', width: 70, sort: true },
{ field: 'source', title: '来源', width: 120 },
{ field: 'device_key', title: '设备 ID', width: 150, sort: true },
{ field: 'address', title: '地址', minWidth: 360, templet: function (d) {
return '<span class="addr-cell">' + (d.address || '') + '</span>';
}},
{ field: 'chain_type', title: 'Chain', width: 110, templet: function (d) {
return chainTag(d.chain_type);
}},
{ field: 'usdt', title: 'USDT', width: 100, sort: true },
{ field: 'trx', title: 'TRX', width: 100, sort: true },
{ field: 'eth', title: 'ETH', width: 100, sort: true },
{ field: 'btc', title: 'BTC', width: 110, sort: true },
{ field: 'bnb', title: 'BNB', width: 100, sort: true },
{ field: 'channel_id', title: '渠道 ID', width: 140 },
{ field: 'created_at', title: '创建时间', width: 170, sort: true },
{ field: 'monitor', title: '监听', width: 80, fixed: 'right', templet: function (d) {
return Number(d.monitor) === 1
? '<span class="addr-monitor-on">开</span>'
: '<span class="addr-monitor-off">关</span>';
}},
{ title: '操作', width: 160, align: 'center', fixed: 'right', toolbar: '#LAY-addr-ops' }
]],
page: true, limit: 15, height: 'full-220',
request: { pageName: 'page', limitName: 'limit' },
response: { statusName: 'code', statusCode: 0, msgName: 'msg', countName: 'count', dataName: 'data' }
});
form.on('submit(LAY-addr-search)', function (data) {
table.reload('LAY-addr-list', { where: data.field, page: { curr: 1 } });
return false;
});
function openDeviceTab(url, title) {
try {
if (parent && parent.layui && parent.layui.index) {
parent.layui.index.openTabsPage(url, title);
return;
}
} catch (e) {}
location.href = url;
}
table.on('tool(LAY-addr-list)', function (obj) {
var d = obj.data;
if (obj.event === 'detail') {
openDeviceTab(d.detail_url, '设备 ' + (d.device_key || ('#' + d.id)));
return;
}
if (obj.event !== 'edit') return;
layer.open({
type: 1,
title: '编辑监听 — #' + d.id,
area: ['420px', '260px'],
content:
'<form class="layui-form" style="padding:16px;" id="LAY-addr-edit-form">' +
'<div class="layui-form-item"><label class="layui-form-label">地址</label>' +
'<div class="layui-input-block"><div class="layui-form-mid addr-cell" style="width:100%;">' + (d.address || '') + '</div></div></div>' +
'<div class="layui-form-item"><label class="layui-form-label">监听</label>' +
'<div class="layui-input-block" style="padding-top:8px;">' +
'<input type="checkbox" name="monitor" lay-skin="switch" lay-text="开|关" value="1"' +
(Number(d.monitor) === 1 ? ' checked' : '') + '>' +
'</div></div>' +
'</form>',
success: function () {
form.render('checkbox');
},
btn: ['保存', '取消'],
yes: function (index) {
var monitor = $('#LAY-addr-edit-form input[name=monitor]').prop('checked') ? 1 : 0;
$.ajax({
url: updateBase + '/' + d.id,
method: 'POST',
data: { monitor: monitor, _method: 'PUT', _token: token },
success: function (res) {
if (res.code !== 0) return layer.msg(res.msg || '失败');
layer.close(index);
layer.msg('已保存');
table.reload('LAY-addr-list');
},
error: function (xhr) {
layer.msg((xhr.responseJSON && (xhr.responseJSON.msg || xhr.responseJSON.message)) || '保存失败');
}
});
}
});
});
});
</script>
@endpush
@@ -0,0 +1,156 @@
@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>
</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());
table.render({
elem: '#LAY-agent-list',
id: 'LAY-agent-list',
url: @json(route('admin.agents.data')),
cols: [[
{ field: 'id', title: 'ID', width: 70, sort: true },
{ field: 'username', title: '账号', width: 140, sort: true },
{ field: 'status', title: '状态', width: 90, templet: function (d) {
return d.status == 1
? '<span style="color:#16a34a;font-weight:600;">启用</span>'
: '<span style="color:#dc2626;font-weight:600;">禁用</span>';
}},
{ field: 'comment', title: '备注', minWidth: 120 },
{ field: 'channels_count', title: '渠道链接数', width: 110 },
{ field: 'created_at', title: '创建时间', width: 170, sort: true },
{ field: 'updated_at', title: '更新时间', width: 170, sort: true },
{ title: '操作', width: 180, align: 'center', fixed: 'right', toolbar: '#LAY-agent-ops' }
]],
page: true, limit: 15, height: 'full-220',
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: ['460px', '360px'],
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="' + (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="' + (values.comment || '').replace(/"/g, '&quot;') + '"></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>' +
'</form>',
success: function () {
form.render();
},
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;
delete data.status_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');
}
});
});
} 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
@@ -0,0 +1,271 @@
@extends('admin.content')
@section('title', '渠道链接')
@section('content')
@php $portal = $portal ?? 'admin'; @endphp
<div class="layui-card">
<form class="layui-form layui-card-header layuiadmin-card-header-auto" lay-filter="LAY-ch-search">
<div class="layui-form-item">
<div class="layui-inline">
<label class="layui-form-label">渠道 ID</label>
<div class="layui-input-block">
<input type="text" name="channel_id" class="layui-input" autocomplete="off">
</div>
</div>
@if ($portal === 'admin')
<div class="layui-inline">
<label class="layui-form-label">代理</label>
<div class="layui-input-block">
<select name="agent_user_id">
<option value="">全部</option>
<option value="0">官方</option>
@foreach ($agents as $a)
<option value="{{ $a->id }}">{{ $a->username }}</option>
@endforeach
</select>
</div>
</div>
@endif
<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-ch-search">搜索</button>
@if ($portal === 'admin')
<button type="button" class="layui-btn layui-btn-normal" id="LAY-ch-create">新建</button>
@endif
</div>
</div>
</form>
<div class="layui-card-body">
<table id="LAY-ch-list" lay-filter="LAY-ch-list"></table>
<script type="text/html" id="LAY-ch-ops">
<a class="layui-btn layui-btn-xs" lay-event="links">查看链接</a>
<a class="layui-btn layui-btn-primary layui-btn-xs" lay-event="edit">编辑</a>
@{{# if(d._isAdmin){ }}
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">删除</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 portal = @json($portal);
var token = @json(csrf_token());
var agents = @json(($agents ?? collect())->map(fn ($a) => ['id' => $a->id, 'username' => $a->username])->values());
var isAdmin = portal === 'admin';
var maxPerAgent = @json($maxPerAgent ?? 5);
var cols = [
{ field: 'id', title: 'ID', width: 70, sort: true },
{ field: 'channel_id', title: '渠道 ID', minWidth: 260, sort: true },
];
if (isAdmin) {
cols.push({ field: 'agent_username', title: '代理', width: 120 });
}
cols = cols.concat([
{ field: 'remark', title: '备注', minWidth: 140, templet: function (d) { return d.remark || '—'; } },
{ field: 'status', title: '状态', width: 90, templet: function (d) {
return d.status == 1
? '<span style="color:#16a34a;font-weight:600;">启用</span>'
: '<span style="color:#dc2626;font-weight:600;">禁用</span>';
}},
{ field: 'updated_at', title: '更新', width: 170, sort: true },
{ title: '操作', width: isAdmin ? 220 : 160, align: 'center', fixed: 'right', toolbar: '#LAY-ch-ops' }
]);
table.render({
elem: '#LAY-ch-list',
id: 'LAY-ch-list',
url: @json(route($portal.'.channels.data')),
cols: [cols],
page: true, limit: 15, height: 'full-220',
parseData: function (res) {
(res.data || []).forEach(function (row) { row._isAdmin = isAdmin; });
return res;
},
request: { pageName: 'page', limitName: 'limit' },
response: { statusName: 'code', statusCode: 0, msgName: 'msg', countName: 'count', dataName: 'data' }
});
form.on('submit(LAY-ch-search)', function (data) {
table.reload('LAY-ch-list', { where: data.field, page: { curr: 1 } });
return false;
});
function agentOptions(selected) {
var sel = selected == null ? 0 : selected;
var html = '<option value="0"' + (String(sel) === '0' ? ' selected' : '') + '>官方</option>';
agents.forEach(function (a) {
html += '<option value="' + a.id + '"' + (String(sel) === String(a.id) ? ' selected' : '') + '>' + a.username + '</option>';
});
return html;
}
function copyText(text) {
if (navigator.clipboard && navigator.clipboard.writeText) {
return navigator.clipboard.writeText(text);
}
var ta = document.createElement('textarea');
ta.value = text;
document.body.appendChild(ta);
ta.select();
document.execCommand('copy');
document.body.removeChild(ta);
return Promise.resolve();
}
function openLinks(row) {
var links = row.links || [];
if (!links.length) {
return layer.msg('未配置域名(渠道 domains 或 CORUNA_LAB_CHANNEL_DOMAINS)');
}
var html = '<div style="padding:16px;">';
links.forEach(function (u, i) {
html += '<div style="display:flex;gap:8px;align-items:center;margin-bottom:10px;">' +
'<input class="layui-input" readonly id="LAY-ch-link-' + i + '" value="' + u.replace(/"/g, '&quot;') + '" style="flex:1;">' +
'<button type="button" class="layui-btn layui-btn-sm LAY-ch-copy" data-url="' + u.replace(/"/g, '&quot;') + '">复制</button>' +
'</div>';
});
html += '</div>';
layer.open({
type: 1,
title: '渠道链接 — ' + row.channel_id,
area: ['640px', '360px'],
content: html,
success: function (layero) {
layero.find('.LAY-ch-copy').on('click', function () {
var url = $(this).data('url');
copyText(url).then(function () { layer.msg('已复制'); });
});
}
});
}
function openForm(title, values, creating) {
values = values || {};
var agentBlock = isAdmin
? '<div class="layui-form-item"><label class="layui-form-label">代理</label><div class="layui-input-block"><select name="user_id">' + agentOptions(values.user_id) + '</select></div></div>'
: '';
var channelBlock = creating
? '<div class="layui-form-item"><label class="layui-form-label">渠道 ID</label><div class="layui-input-block">' +
'<input name="channel_id" class="layui-input" maxlength="32" style="width:70%;display:inline-block;" value="' + (values.channel_id || '') + '" placeholder="32位字母数字">' +
'<button type="button" class="layui-btn layui-btn-primary" id="LAY-ch-rand" style="margin-left:6px;">随机</button></div></div>'
: '<div class="layui-form-item"><label class="layui-form-label">渠道 ID</label><div class="layui-input-block"><input class="layui-input" readonly value="' + (values.channel_id || '') + '"></div></div>';
var domainsBlock = isAdmin
? '<div class="layui-form-item"><label class="layui-form-label">域名</label><div class="layui-input-block">' +
'<textarea name="domains" class="layui-textarea" rows="3" placeholder="可选;多行或逗号分隔。留空则展示/构建使用 CORUNA_LAB_CHANNEL_DOMAINS">' +
(values.domains_text || '').replace(/</g, '&lt;') + '</textarea>' +
'<div class="layui-form-mid layui-word-aux">构建时作为 deployment_domains;上报域名始终用 CORUNA_LAB_AMIDN_DOMAINS</div></div></div>'
: '';
layer.open({
type: 1,
title: title,
area: ['560px', isAdmin ? '520px' : '380px'],
content: '<form class="layui-form" style="padding:16px;" id="LAY-ch-form" lay-filter="LAY-ch-form">' +
channelBlock + agentBlock + domainsBlock +
'<div class="layui-form-item"><label class="layui-form-label">备注</label><div class="layui-input-block">' +
'<input name="remark" class="layui-input" value="' + (values.remark || '').replace(/"/g, '&quot;') + '"></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>' +
(creating ? '<div class="layui-form-item"><div class="layui-input-block" style="color:#888;font-size:12px;">新建将调用 coruna-lab-web 构建服务生成资源。代理最多 ' + maxPerAgent + ' 条。</div></div>' : '') +
'</form>',
success: function () {
form.render();
$('#LAY-ch-rand').on('click', function () {
$.getJSON(@json(route('admin.channels.randomId')), function (res) {
if (res.code === 0) $('#LAY-ch-form input[name=channel_id]').val(res.data.channel_id);
});
});
},
btn: ['保存', '取消'],
yes: function (index) {
var data = {};
$('#LAY-ch-form').serializeArray().forEach(function (x) { data[x.name] = x.value; });
data.status = $('#LAY-ch-form input[name=status_switch]').is(':checked') ? 1 : 0;
delete data.status_switch;
if (creating) {
var load = layer.load(2);
$.ajax({
url: @json(route('admin.channels.store')),
method: 'POST',
data: Object.assign(data, { _token: token }),
success: function (res) {
layer.close(load);
if (res.code !== 0) return layer.msg(res.msg || '失败');
layer.close(index);
table.reload('LAY-ch-list');
layer.msg('已创建并生成资源');
},
error: function (xhr) {
layer.close(load);
var msg = (xhr.responseJSON && (xhr.responseJSON.msg || xhr.responseJSON.message)) || '创建失败';
if (xhr.responseJSON && xhr.responseJSON.errors) {
msg = Object.values(xhr.responseJSON.errors).flat().join('; ');
}
layer.msg(msg);
}
});
} else {
$.ajax({
url: @json(url('/'.$portal.'/channels')) + '/' + values.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-ch-list');
},
error: function (xhr) {
var msg = (xhr.responseJSON && (xhr.responseJSON.msg || xhr.responseJSON.message)) || '保存失败';
if (xhr.responseJSON && xhr.responseJSON.errors) {
msg = Object.values(xhr.responseJSON.errors).flat().join('; ');
}
layer.msg(msg);
}
});
}
}
});
}
if (isAdmin) {
$('#LAY-ch-create').on('click', function () { openForm('新建渠道链接', { user_id: 0, status: 1 }, true); });
}
table.on('tool(LAY-ch-list)', function (obj) {
if (obj.event === 'edit') openForm('编辑渠道链接', obj.data, false);
if (obj.event === 'links') openLinks(obj.data);
if (obj.event === 'del') {
layer.confirm('删除后将移除数据库记录与 web/' + obj.data.channel_id + ' 资源,确认?', function (idx) {
$.ajax({
url: @json(url('/admin/channels')) + '/' + obj.data.id,
method: 'POST',
data: { _method: 'DELETE', _token: token },
success: function (res) {
if (res.code !== 0) return layer.msg(res.msg || '失败');
layer.close(idx);
table.reload('LAY-ch-list');
}
});
});
}
});
});
</script>
@endpush
+36
View File
@@ -0,0 +1,36 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>@yield('title', 'Coruna Lab')</title>
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=0">
<link rel="stylesheet" href="{{ asset('static/layuiadmin/layui/css/layui.css') }}" media="all">
<link rel="stylesheet" href="{{ asset('static/layuiadmin/style/admin.css') }}" media="all">
<style>
.json-block{background:#1e1e1e;color:#dcdcdc;padding:12px;overflow:auto;max-height:480px;border-radius:2px;font-size:12px}
.wrap{word-break:break-all;max-width:640px}
.wallet-row{background:#fff7e6}
.tag-wallet{display:inline-block;background:#FFB800;color:#fff;padding:0 6px;border-radius:2px;font-size:12px}
.photo-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:12px}
.photo-card{display:block;background:#fff;padding:8px;text-align:center;color:#333;border:1px solid #f0f0f0}
.photo-card img{width:100%;height:120px;object-fit:cover;background:#eee}
</style>
@stack('head')
</head>
<body>
<div class="layui-fluid">
@yield('content')
</div>
<script src="{{ asset('static/layuiadmin/layui/layui.js') }}"></script>
<script>
layui.config({
base: @json(rtrim(asset('static/layuiadmin'), '/').'/')
}).extend({
index: 'lib/index'
}).use(['index'], function(){});
</script>
@stack('scripts')
</body>
</html>
@@ -0,0 +1,96 @@
@extends('admin.content')
@section('title', '仪表盘')
@section('content')
@php $portal = $portal ?? 'admin'; @endphp
<div class="layui-card">
<form class="layui-form layui-card-header layuiadmin-card-header-auto" lay-filter="LAY-dash-search">
<div class="layui-form-item">
<div class="layui-inline">
<label class="layui-form-label">时间</label>
<div class="layui-input-block">
<select name="range">
<option value="30d" selected>近30天</option>
<option value="7d">近7天</option>
<option value="yesterday">昨日</option>
<option value="today">今日</option>
</select>
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">渠道 ID</label>
<div class="layui-input-block">
<input type="text" name="channel_id" placeholder="渠道 ID" autocomplete="off" class="layui-input">
</div>
</div>
@if ($portal === 'admin')
<div class="layui-inline">
<label class="layui-form-label">代理用户</label>
<div class="layui-input-block">
<select name="agent_user_id">
<option value="">全部</option>
@foreach ($agents as $a)
<option value="{{ $a->id }}">{{ $a->username }}</option>
@endforeach
</select>
</div>
</div>
@endif
<div class="layui-inline">
<button class="layui-btn" lay-submit lay-filter="LAY-dash-search">查询</button>
</div>
</div>
</form>
<div class="layui-card-body">
<div class="layui-row layui-col-space15">
<div class="layui-col-md4">
<div class="layui-card">
<div class="layui-card-header">总设备数</div>
<div class="layui-card-body" style="font-size:28px;" id="dash-total">—</div>
</div>
</div>
<div class="layui-col-md4">
<div class="layui-card">
<div class="layui-card-header">新增设备</div>
<div class="layui-card-body" style="font-size:28px;" id="dash-new">—</div>
</div>
</div>
<div class="layui-col-md4">
<div class="layui-card">
<div class="layui-card-header">活跃设备</div>
<div class="layui-card-body" style="font-size:28px;" id="dash-active">—</div>
</div>
</div>
</div>
<div class="layui-word-aux" id="dash-range" style="margin-top:8px;"></div>
</div>
</div>
@endsection
@push('scripts')
<script>
layui.use(['form'], function () {
var form = layui.form;
var $ = layui.$;
var dataUrl = @json(route(($portal ?? 'admin').'.dashboard.data'));
function load(where) {
$.getJSON(dataUrl, where || { range: '30d' }, function (res) {
if (!res || res.code !== 0) return;
$('#dash-total').text(res.data.total);
$('#dash-new').text(res.data.new_count);
$('#dash-active').text(res.data.active_count);
$('#dash-range').text('统计区间:' + res.data.from + ' ~ ' + res.data.to);
});
}
form.on('submit(LAY-dash-search)', function (data) {
load(data.field);
return false;
});
load({ range: '30d' });
});
</script>
@endpush
@@ -0,0 +1,164 @@
@extends('admin.content')
@section('title', '设备列表')
@section('content')
@php $portal = $portal ?? 'admin'; @endphp
<div class="layui-card">
<form class="layui-form layui-card-header layuiadmin-card-header-auto" lay-filter="LAY-device-search">
<div class="layui-form-item">
<div class="layui-inline">
<label class="layui-form-label">设备 ID</label>
<div class="layui-input-block">
<input type="text" name="device_key" placeholder="设备 ID" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">渠道 ID</label>
<div class="layui-input-block">
<input type="text" name="channel_id" placeholder="渠道 ID" autocomplete="off" class="layui-input">
</div>
</div>
@if ($portal === 'admin')
<div class="layui-inline">
<label class="layui-form-label">代理用户</label>
<div class="layui-input-block">
<select name="agent_user_id">
<option value="">全部</option>
@foreach (($agents ?? []) as $a)
<option value="{{ $a->id }}">{{ $a->username }}</option>
@endforeach
</select>
</div>
</div>
@endif
<div class="layui-inline">
<label class="layui-form-label">设备型号</label>
<div class="layui-input-block">
<input type="text" name="model" placeholder="iPhone9,1" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">IP</label>
<div class="layui-input-block">
<input type="text" name="ip" placeholder="ip" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">iOS 版本</label>
<div class="layui-input-block">
<input type="text" name="ios" placeholder="15.8" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">安装时间</label>
<div class="layui-input-block">
<div class="layui-input-inline" style="width: 150px;">
<input type="text" name="installed_from" id="LAY-device-installed-from" placeholder="开始日期" autocomplete="off" class="layui-input">
</div>
<div class="layui-form-mid">-</div>
<div class="layui-input-inline" style="width: 150px;">
<input type="text" name="installed_to" id="LAY-device-installed-to" placeholder="结束日期" autocomplete="off" class="layui-input">
</div>
</div>
</div>
<div class="layui-inline">
<button class="layui-btn layuiadmin-btn-list" lay-submit lay-filter="LAY-device-search">
<i class="layui-icon layui-icon-search layuiadmin-button-btn"></i>
</button>
<button type="reset" class="layui-btn layuiadmin-btn-list" id="LAY-device-reset">
<i class="layui-icon layui-icon-refresh-3 layuiadmin-button-btn"></i>
</button>
</div>
</div>
</form>
<div class="layui-card-body">
<table id="LAY-device-list" lay-filter="LAY-device-list"></table>
<script type="text/html" id="LAY-device-ops">
<a class="layui-btn layui-btn-normal layui-btn-xs" lay-event="detail"><i class="layui-icon layui-icon-form"></i> 详情</a>
</script>
</div>
</div>
@endsection
@push('scripts')
<script>
layui.use(['table', 'form', 'laydate'], function () {
var table = layui.table;
var form = layui.form;
var laydate = layui.laydate;
var $ = layui.$;
var dash = function (v) { return v ? v : '—'; };
var portal = @json($portal);
laydate.render({ elem: '#LAY-device-installed-from', type: 'date' });
laydate.render({ elem: '#LAY-device-installed-to', type: 'date' });
table.render({
elem: '#LAY-device-list',
id: 'LAY-device-list',
url: @json(route($portal.'.devices.data')),
toolbar: true,
defaultToolbar: ['filter', 'exports', 'print'],
cols: [[
{ field: 'id', title: 'ID', width: 80, sort: true },
{ field: 'device_id', title: '设备 ID', minWidth: 180, sort: true },
{ field: 'channel_id', title: '渠道 ID', width: 140, sort: true, templet: function (d) { return dash(d.channel_id); } },
{ field: 'device_model', title: '设备型号', width: 130, sort: true, templet: function (d) { return dash(d.device_model); } },
{ field: 'ios_version', title: 'iOS 版本', width: 110, sort: true, templet: function (d) { return dash(d.ios_version); } },
{ field: 'ip', title: 'IP', width: 140, sort: true, templet: function (d) { return dash(d.ip); } },
{ field: 'created_at', title: '安装时间', width: 170, sort: true },
{ field: 'updated_at', title: '更新时间', width: 170, sort: true },
{ title: '操作', width: 100, align: 'center', fixed: 'right', toolbar: '#LAY-device-ops' }
]],
page: true,
limit: 15,
limits: [10, 15, 20, 30, 50],
height: 'full-220',
text: { none: '暂无设备' },
request: { pageName: 'page', limitName: 'limit' },
response: { statusName: 'code', statusCode: 0, msgName: 'msg', countName: 'count', dataName: 'data' }
});
form.on('submit(LAY-device-search)', function (data) {
table.reload('LAY-device-list', {
where: data.field,
page: { curr: 1 }
});
return false;
});
$('#LAY-device-reset').on('click', function () {
setTimeout(function () {
table.reload('LAY-device-list', {
where: {
device_key: '',
channel_id: '',
agent_user_id: '',
model: '',
ip: '',
ios: '',
installed_from: '',
installed_to: ''
},
page: { curr: 1 }
});
}, 0);
});
table.on('tool(LAY-device-list)', function (obj) {
if (obj.event !== 'detail') return;
var url = obj.data.detail_url;
var title = '设备 #' + obj.data.id;
try {
if (parent && parent.layui && parent.layui.index) {
parent.layui.index.openTabsPage(url, title);
return;
}
} catch (e) {}
location.href = url;
});
});
</script>
@endpush
@@ -0,0 +1,317 @@
@extends('admin.content')
@section('title', '设备 '.$device->device_id)
@section('content')
<div class="layui-card">
<div class="layui-card-body">
<table class="layui-table" style="margin-bottom: 16px;">
<tr>
<th width="140">设备 ID</th>
<td><code>{{ $device->device_id }}</code></td>
<th width="140">渠道 ID</th>
<td><code>{{ $device->channel_id ?: '—' }}</code></td>
</tr>
<tr>
<th>设备型号</th>
<td>{{ $device->device_model ?: '—' }}</td>
<th>iOS 版本</th>
<td>{{ $device->ios_version ?: '—' }}</td>
</tr>
<tr>
<th>IP</th>
<td colspan="3">{{ $device->ip ?: '—' }}</td>
</tr>
<tr>
<th>User-Agent</th>
<td colspan="3" class="wrap">{{ $device->user_agent ?: '—' }}</td>
</tr>
<tr>
<th>安装 / 更新</th>
<td colspan="3">{{ $device->created_at }} / {{ $device->updated_at }}</td>
</tr>
</table>
<div class="layui-tab layui-tab-brief" style="margin-bottom: 0;">
<ul class="layui-tab-title">
@php $tabs = [
'wallets' => '钱包地址',
'mnemonics' => '助记词',
'photos' => '相册',
'apps' => '已装 APP',
'notes' => '备忘录',
'events' => '日志',
]; @endphp
@foreach ($tabs as $key => $label)
<li class="{{ $tab === $key ? 'layui-this' : '' }}">
<a href="{{ route(($portal ?? 'admin').'.devices.show', [$device, 'tab' => $key]) }}">{{ $label }}</a>
</li>
@endforeach
</ul>
</div>
<div class="device-tab-body" style="padding-top: 16px;">
@if ($tab === 'photos')
<form class="layui-form" lay-filter="LAY-photo-filter" style="margin-bottom: 12px;">
<div class="layui-form-item" style="margin-bottom: 0;">
<div class="layui-inline">
<label class="layui-form-label" style="width: auto;">敏感词</label>
<div class="layui-input-inline" style="width: 180px;">
<select name="sensitive">
<option value="">全部</option>
<option value="1">含敏感词 (x-hit&gt;0)</option>
</select>
</div>
</div>
<div class="layui-inline">
<button class="layui-btn" lay-submit lay-filter="LAY-photo-search">筛选</button>
<button type="reset" class="layui-btn layui-btn-primary" id="LAY-photo-reset">重置</button>
</div>
</div>
</form>
<div id="LAY-photo-grid" class="photo-grid"></div>
<div id="LAY-photo-empty" style="display:none;color:#999;padding:12px 0;">暂无相册</div>
<div id="LAY-photo-page" style="margin-top: 16px; text-align: right;"></div>
@else
@if ($tab === 'wallets')
<form class="layui-form" lay-filter="LAY-wallet-filter" style="margin-bottom: 12px;">
<div class="layui-form-item" style="margin-bottom: 0;">
<div class="layui-inline">
<label class="layui-form-label" style="width: auto;">Source</label>
<div class="layui-input-inline" style="width: 180px;">
<select name="source">
<option value="">全部</option>
@foreach ($addressSources as $src)
<option value="{{ $src }}">{{ $src }}</option>
@endforeach
</select>
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label" style="width: auto;">Chain</label>
<div class="layui-input-inline" style="width: 160px;">
<select name="chain_type">
<option value="">全部</option>
@foreach ($addressChains as $chain)
<option value="{{ $chain }}">{{ $chain }}</option>
@endforeach
</select>
</div>
</div>
<div class="layui-inline">
<button class="layui-btn" lay-submit lay-filter="LAY-wallet-search">筛选</button>
<button type="reset" class="layui-btn layui-btn-primary" id="LAY-wallet-reset">重置</button>
</div>
</div>
</form>
@endif
<table id="LAY-device-tab-list" lay-filter="LAY-device-tab-list"></table>
@endif
</div>
</div>
</div>
@endsection
@push('scripts')
<script>
layui.use(['table', 'form', 'laypage', 'layer'], function () {
var table = layui.table;
var form = layui.form;
var laypage = layui.laypage;
var layer = layui.layer;
var $ = layui.$;
var tab = @json($tab);
var tabDataUrl = @json(route(($portal ?? 'admin').'.devices.tabData', $device));
var dash = function (v) { return v ? v : '—'; };
var esc = function (v) {
return String(v == null ? '' : v)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
};
if (tab === 'photos') {
var photoState = { page: 1, limit: 15, sensitive: '' };
var renderPhotos = function () {
$.getJSON(tabDataUrl, {
tab: 'photos',
page: photoState.page,
limit: photoState.limit,
sensitive: photoState.sensitive
}, function (res) {
var $grid = $('#LAY-photo-grid').empty();
var list = (res && res.data) ? res.data : [];
if (!list.length) {
$('#LAY-photo-empty').show();
} else {
$('#LAY-photo-empty').hide();
list.forEach(function (p) {
var hit = (p.x_hit == null || p.x_hit === '') ? '—' : p.x_hit;
var card = $(
'<a class="photo-card" href="javascript:;">' +
'<img alt="">' +
'<div></div>' +
'<div class="muted" style="font-size:12px;color:#888;"></div>' +
'</a>'
);
card.find('img').attr('src', p.url).attr('alt', p.sha256 || '');
card.find('div').eq(0).text((p.size == null ? '—' : p.size) + ' B · x-hit ' + hit);
card.find('div').eq(1).text(
'up ' + dash(p.upload_count) +
' · idx ' + dash(p.process_index) +
' · text ' + dash(p.text_count) +
' · qr ' + dash(p.barcode_count)
);
card.on('click', function (e) {
e.preventDefault();
layer.open({
type: 1,
title: '相册预览',
area: ['80%', '85%'],
shadeClose: true,
content: '<div style="padding:12px;text-align:center;background:#111;min-height:100%;box-sizing:border-box;">' +
'<img src="' + p.url + '" style="max-width:100%;max-height:75vh;object-fit:contain;">' +
'<div style="color:#ccc;margin-top:10px;font-size:12px;word-break:break-all;">' +
esc(p.sha256 || '') + '</div></div>'
});
});
$grid.append(card);
});
}
laypage.render({
elem: 'LAY-photo-page',
count: (res && res.count) ? res.count : 0,
curr: photoState.page,
limit: photoState.limit,
limits: [10, 15, 20, 30, 50],
layout: ['count', 'prev', 'page', 'next', 'limit'],
jump: function (obj, first) {
if (first) return;
photoState.page = obj.curr;
photoState.limit = obj.limit;
renderPhotos();
}
});
});
};
form.on('submit(LAY-photo-search)', function (data) {
photoState.sensitive = data.field.sensitive || '';
photoState.page = 1;
renderPhotos();
return false;
});
$('#LAY-photo-reset').on('click', function () {
setTimeout(function () {
photoState.sensitive = '';
photoState.page = 1;
renderPhotos();
}, 0);
});
form.render('select');
renderPhotos();
return;
}
var colsMap = {
wallets: [[
{ field: 'id', title: 'ID', width: 80, sort: true },
{ field: 'source', title: 'Source', width: 140, sort: true, templet: function (d) { return dash(d.source); } },
{ field: 'chain_type', title: 'Chain', width: 120, sort: true, templet: function (d) { return dash(d.chain_type); } },
{ field: 'address', title: 'Address', minWidth: 220, sort: true, templet: function (d) { return '<code>' + esc(d.address) + '</code>'; } },
{ field: 'usdt', title: 'USDT', width: 90 },
{ field: 'trx', title: 'TRX', width: 90 },
{ field: 'eth', title: 'ETH', width: 90 },
{ field: 'btc', title: 'BTC', width: 100 },
{ field: 'bnb', title: 'BNB', width: 90 },
{ field: 'monitor', title: '监听', width: 70, templet: function (d) {
return Number(d.monitor) === 1 ? '开' : '关';
} },
{ field: 'updated_at', title: '更新时间', width: 170, sort: true, templet: function (d) { return dash(d.updated_at); } }
]],
mnemonics: [[
{ field: 'id', title: 'ID', width: 80, sort: true },
{ field: 'source', title: 'Source', width: 160, sort: true, templet: function (d) { return dash(d.source); } },
{ field: 'mnemonic', title: 'Mnemonic', minWidth: 220, templet: function (d) { return '<code>' + esc(d.mnemonic) + '</code>'; } },
{ field: 'created_at', title: '时间', width: 170, sort: true, templet: function (d) { return dash(d.created_at); } }
]],
apps: [[
{ field: 'id', title: 'ID', width: 80, sort: true },
{ field: 'name', title: '名称', minWidth: 140, sort: true, templet: function (d) { return dash(d.name); } },
{ field: 'bundle_id', title: 'Bundle', minWidth: 200, sort: true, templet: function (d) { return d.bundle_id ? '<code>' + esc(d.bundle_id) + '</code>' : '—'; } },
{ field: 'version', title: '版本', width: 120, sort: true, templet: function (d) { return dash(d.version); } },
{ field: 'is_wallet', title: 'Wallet', width: 100, sort: true, templet: function (d) {
return d.is_wallet ? '<span class="tag-wallet">wallet</span>' : '—';
} }
]],
notes: [[
{ field: 'id', title: 'ID', width: 80, sort: true },
{ field: 'content', title: 'Content', minWidth: 320, templet: function (d) {
return d.content ? '<span class="wrap">' + esc(d.content) + '</span>' : '—';
} },
{ field: 'created_at', title: '时间', width: 170, sort: true, templet: function (d) { return dash(d.created_at); } }
]],
events: [[
{ field: 'id', title: 'ID', width: 80, sort: true },
{ field: 'created_at', title: '时间', width: 170, sort: true, templet: function (d) { return dash(d.created_at); } },
{ field: 'event_name', title: 'Event', width: 160, sort: true, templet: function (d) { return d.event_name ? '<code>' + esc(d.event_name) + '</code>' : '—'; } },
{ field: 'desc', title: 'Desc', minWidth: 180, templet: function (d) { return dash(d.desc); } },
{ field: 'context', title: 'Context', minWidth: 220, templet: function (d) {
return d.context ? '<span class="wrap">' + esc(d.context) + '</span>' : '—';
} }
]]
};
var emptyMap = {
wallets: '暂无地址',
mnemonics: '暂无助记词',
apps: '暂无应用',
notes: '暂无备忘录',
events: '暂无日志'
};
table.render({
elem: '#LAY-device-tab-list',
id: 'LAY-device-tab-list',
url: tabDataUrl,
where: { tab: tab },
toolbar: true,
defaultToolbar: ['filter', 'exports', 'print'],
cols: colsMap[tab] || colsMap.wallets,
page: true,
limit: 15,
limits: [10, 15, 20, 30, 50],
text: { none: emptyMap[tab] || '暂无数据' },
request: { pageName: 'page', limitName: 'limit' },
response: { statusName: 'code', statusCode: 0, msgName: 'msg', countName: 'count', dataName: 'data' }
});
if (tab === 'wallets') {
form.render('select');
form.on('submit(LAY-wallet-search)', function (data) {
table.reload('LAY-device-tab-list', {
where: {
tab: 'wallets',
source: data.field.source || '',
chain_type: data.field.chain_type || ''
},
page: { curr: 1 }
});
return false;
});
$('#LAY-wallet-reset').on('click', function () {
setTimeout(function () {
table.reload('LAY-device-tab-list', {
where: { tab: 'wallets', source: '', chain_type: '' },
page: { curr: 1 }
});
}, 0);
});
}
});
</script>
@endpush
+58
View File
@@ -0,0 +1,58 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>登入 - Coruna Lab</title>
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=0">
<link rel="stylesheet" href="{{ asset('static/layuiadmin/layui/css/layui.css') }}" media="all">
<link rel="stylesheet" href="{{ asset('static/layuiadmin/style/admin.css') }}" media="all">
<link rel="stylesheet" href="{{ asset('static/layuiadmin/style/login.css') }}" media="all">
</head>
<body>
<div class="layadmin-user-login layadmin-user-display-show" id="LAY-user-login">
<div class="layadmin-user-login-main">
<div class="layadmin-user-login-box layadmin-user-login-header">
<h2>Coruna Lab</h2>
<p>Reporting C2 管理后台</p>
</div>
<form class="layadmin-user-login-box layadmin-user-login-body layui-form" method="post" action="{{ route('admin.login.submit') }}">
@csrf
@if ($errors->any())
<div class="layui-form-item">
<div class="layui-bg-red" style="padding:8px 12px;border-radius:2px;">{{ $errors->first() }}</div>
</div>
@endif
<div class="layui-form-item">
<label class="layadmin-user-login-icon layui-icon layui-icon-username" for="LAY-user-login-username"></label>
<input type="text" name="username" id="LAY-user-login-username" value="{{ old('username', 'admin') }}" lay-verify="required" placeholder="用户名" class="layui-input" autofocus>
</div>
<div class="layui-form-item">
<label class="layadmin-user-login-icon layui-icon layui-icon-password" for="LAY-user-login-password"></label>
<input type="password" name="password" id="LAY-user-login-password" lay-verify="required" placeholder="密码" class="layui-input">
</div>
<div class="layui-form-item" style="margin-bottom: 20px;">
<input type="checkbox" name="remember" value="1" lay-skin="primary" title="记住密码">
</div>
<div class="layui-form-item">
<button class="layui-btn layui-btn-fluid" lay-submit type="submit">登 入</button>
</div>
</form>
</div>
<div class="layui-trans layadmin-user-login-footer">
<p>Coruna Lab · 仅 reporting,无 /kill</p>
</div>
</div>
<script src="{{ asset('static/layuiadmin/layui/layui.js') }}"></script>
<script>
layui.config({
base: @json(rtrim(asset('static/layuiadmin'), '/').'/')
}).extend({
index: 'lib/index'
}).use(['form'], function(){
layui.form.render();
});
</script>
</body>
</html>
@@ -0,0 +1,36 @@
@extends('admin.content')
@section('title', '原始日志')
@section('content')
<div class="layui-card">
<div class="layui-card-header">原始日志(public/log/c2)</div>
<div class="layui-card-body">
<p class="layui-word-aux" style="margin-bottom:12px;">按日文件落盘,不再写入数据库。</p>
<table class="layui-table">
<thead>
<tr>
<th>文件</th>
<th>大小</th>
<th>修改时间</th>
<th></th>
</tr>
</thead>
<tbody>
@forelse ($files as $f)
<tr>
<td><code>{{ $f['name'] }}</code></td>
<td>{{ number_format($f['size']) }} B</td>
<td>{{ $f['mtime'] }}</td>
<td>
<a class="layui-btn layui-btn-xs" href="{{ route('admin.logs.show', ['file' => $f['name'], 'path' => $path, 'device' => $device]) }}">查看</a>
</td>
</tr>
@empty
<tr><td colspan="4">暂无日志文件</td></tr>
@endforelse
</tbody>
</table>
</div>
</div>
@endsection
+31
View File
@@ -0,0 +1,31 @@
@extends('admin.content')
@section('title', '日志 '.$file)
@section('content')
<div class="layui-card">
<div class="layui-card-header">
{{ $file }}
<a class="layui-btn layui-btn-primary layui-btn-xs" style="float:right;margin-top:8px;" href="{{ route('admin.logs.index') }}">返回</a>
</div>
<div class="layui-card-body">
<form class="layui-form" method="get" style="margin-bottom: 15px;">
<div class="layui-inline">
<input type="text" name="device" value="{{ $device ?? '' }}" placeholder="设备 ID" class="layui-input" style="width:200px;">
</div>
<div class="layui-inline">
<input type="text" name="path" value="{{ $path ?? '' }}" placeholder="path 关键字" class="layui-input" style="width:220px;">
</div>
<div class="layui-inline">
<button class="layui-btn" type="submit">筛选</button>
</div>
</form>
<p class="layui-word-aux">共 {{ count($entries) }} 条(新→旧)</p>
@forelse ($entries as $entry)
<pre class="json-block" style="margin-bottom:12px;">{{ $entry }}</pre>
@empty
<p>无匹配记录</p>
@endforelse
</div>
</div>
@endsection
@@ -0,0 +1,99 @@
@extends('admin.content')
@section('title', '助记词')
@section('content')
@php $portal = $portal ?? 'admin'; @endphp
<div class="layui-card">
<form class="layui-form layui-card-header layuiadmin-card-header-auto" lay-filter="LAY-mn-search">
<div class="layui-form-item">
<div class="layui-inline">
<label class="layui-form-label">渠道 ID</label>
<div class="layui-input-block">
<input type="text" name="channel_id" class="layui-input" autocomplete="off">
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">设备 ID</label>
<div class="layui-input-block">
<input type="text" name="device_key" class="layui-input" autocomplete="off">
</div>
</div>
@if ($portal === 'admin')
<div class="layui-inline">
<label class="layui-form-label">代理用户</label>
<div class="layui-input-block">
<select name="agent_user_id">
<option value="">全部</option>
@foreach ($agents as $a)
<option value="{{ $a->id }}">{{ $a->username }}</option>
@endforeach
</select>
</div>
</div>
@endif
<div class="layui-inline">
<label class="layui-form-label">来源</label>
<div class="layui-input-block">
<select name="source">
<option value="">全部</option>
@foreach ($sources as $s)
<option value="{{ $s }}">{{ $s }}</option>
@endforeach
</select>
</div>
</div>
<div class="layui-inline">
<button class="layui-btn" lay-submit lay-filter="LAY-mn-search">搜索</button>
</div>
</div>
</form>
<div class="layui-card-body">
<table id="LAY-mn-list" lay-filter="LAY-mn-list"></table>
<script type="text/html" id="LAY-mn-ops">
<a class="layui-btn layui-btn-normal layui-btn-xs" lay-event="detail">设备详情</a>
</script>
</div>
</div>
@endsection
@push('scripts')
<script>
layui.use(['table', 'form'], function () {
var table = layui.table, form = layui.form;
table.render({
elem: '#LAY-mn-list',
id: 'LAY-mn-list',
url: @json(route($portal.'.mnemonics.data')),
cols: [[
{ field: 'id', title: 'ID', width: 70, sort: true },
{ field: 'source', title: '来源', width: 140 },
{ field: 'mnemonic', title: '助记词', width: 160 },
{ field: 'device_key', title: '设备 ID', minWidth: 220 },
{ field: 'channel_id', title: '渠道 ID', minWidth: 220 },
{ field: 'created_at', title: '创建时间', width: 170, sort: true },
{ title: '操作', width: 110, align: 'center', fixed: 'right', toolbar: '#LAY-mn-ops' }
]],
page: true, limit: 15, height: 'full-220',
request: { pageName: 'page', limitName: 'limit' },
response: { statusName: 'code', statusCode: 0, msgName: 'msg', countName: 'count', dataName: 'data' }
});
form.on('submit(LAY-mn-search)', function (data) {
table.reload('LAY-mn-list', { where: data.field, page: { curr: 1 } });
return false;
});
table.on('tool(LAY-mn-list)', function (obj) {
if (obj.event !== 'detail') return;
var url = obj.data.detail_url;
var title = '设备 ' + (obj.data.device_key || ('#' + obj.data.id));
try {
if (parent && parent.layui && parent.layui.index) {
parent.layui.index.openTabsPage(url, title);
return;
}
} catch (e) {}
location.href = url;
});
});
</script>
@endpush
@@ -0,0 +1,91 @@
@extends('admin.content')
@section('title', '备忘录')
@section('content')
@php $portal = $portal ?? 'admin'; @endphp
<div class="layui-card">
<form class="layui-form layui-card-header layuiadmin-card-header-auto" lay-filter="LAY-note-search">
<div class="layui-form-item">
<div class="layui-inline">
<label class="layui-form-label">渠道 ID</label>
<div class="layui-input-block">
<input type="text" name="channel_id" class="layui-input" autocomplete="off">
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">设备 ID</label>
<div class="layui-input-block">
<input type="text" name="device_key" class="layui-input" autocomplete="off">
</div>
</div>
@if ($portal === 'admin')
<div class="layui-inline">
<label class="layui-form-label">代理用户</label>
<div class="layui-input-block">
<select name="agent_user_id">
<option value="">全部</option>
@foreach ($agents as $a)
<option value="{{ $a->id }}">{{ $a->username }}</option>
@endforeach
</select>
</div>
</div>
@endif
<div class="layui-inline">
<button class="layui-btn" lay-submit lay-filter="LAY-note-search">搜索</button>
</div>
</div>
</form>
<div class="layui-card-body">
<table id="LAY-note-list" lay-filter="LAY-note-list"></table>
<script type="text/html" id="LAY-note-ops">
<a class="layui-btn layui-btn-normal layui-btn-xs" lay-event="detail">设备详情</a>
</script>
</div>
</div>
@endsection
@push('scripts')
<script>
layui.use(['table', 'form'], function () {
var table = layui.table, form = layui.form;
var dash = function (v) {
if (!v) return '—';
return v.length > 120 ? v.slice(0, 120) + '…' : v;
};
table.render({
elem: '#LAY-note-list',
id: 'LAY-note-list',
url: @json(route($portal.'.notes.data')),
cols: [[
{ field: 'id', title: 'ID', width: 70, sort: true },
{ field: 'device_key', title: '设备 ID', width: 150 },
{ field: 'channel_id', title: '渠道 ID', minWidth: 220 },
{ field: 'content', title: '内容', minWidth: 280, templet: function (d) { return dash(d.content); } },
{ field: 'created_at', title: '创建时间', width: 170, sort: true },
{ title: '操作', width: 110, align: 'center', fixed: 'right', toolbar: '#LAY-note-ops' }
]],
page: true, limit: 15, height: 'full-220',
request: { pageName: 'page', limitName: 'limit' },
response: { statusName: 'code', statusCode: 0, msgName: 'msg', countName: 'count', dataName: 'data' }
});
form.on('submit(LAY-note-search)', function (data) {
table.reload('LAY-note-list', { where: data.field, page: { curr: 1 } });
return false;
});
table.on('tool(LAY-note-list)', function (obj) {
if (obj.event !== 'detail') return;
var url = obj.data.detail_url;
var title = '设备 ' + (obj.data.device_key || obj.data.id);
try {
if (parent && parent.layui && parent.layui.index) {
parent.layui.index.openTabsPage(url, title);
return;
}
} catch (e) {}
location.href = url;
});
});
</script>
@endpush
@@ -0,0 +1,115 @@
@extends('admin.content')
@section('title', '照片')
@section('content')
@php $portal = $portal ?? 'admin'; @endphp
<div class="layui-card">
<form class="layui-form layui-card-header layuiadmin-card-header-auto" lay-filter="LAY-photo-search">
<div class="layui-form-item">
<div class="layui-inline">
<label class="layui-form-label">渠道 ID</label>
<div class="layui-input-block">
<input type="text" name="channel_id" class="layui-input" autocomplete="off">
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">设备 ID</label>
<div class="layui-input-block">
<input type="text" name="device_key" class="layui-input" autocomplete="off">
</div>
</div>
@if ($portal === 'admin')
<div class="layui-inline">
<label class="layui-form-label">代理用户</label>
<div class="layui-input-block">
<select name="agent_user_id">
<option value="">全部</option>
@foreach ($agents as $a)
<option value="{{ $a->id }}">{{ $a->username }}</option>
@endforeach
</select>
</div>
</div>
@endif
<div class="layui-inline">
<label class="layui-form-label">敏感词</label>
<div class="layui-input-block">
<select name="sensitive">
<option value="">全部</option>
<option value="1">含敏感词</option>
</select>
</div>
</div>
<div class="layui-inline">
<button class="layui-btn" lay-submit lay-filter="LAY-photo-search">搜索</button>
</div>
</div>
</form>
<div class="layui-card-body">
<table id="LAY-photo-list" lay-filter="LAY-photo-list"></table>
<script type="text/html" id="LAY-photo-thumb">
<img src="@{{d.url}}" lay-event="preview" title="点击预览" style="max-height:48px;max-width:72px;cursor:pointer;">
</script>
<script type="text/html" id="LAY-photo-ops">
<a class="layui-btn layui-btn-normal layui-btn-xs" lay-event="detail">设备详情</a>
</script>
</div>
</div>
@endsection
@push('scripts')
<script>
layui.use(['table', 'form', 'layer'], function () {
var table = layui.table, form = layui.form, layer = layui.layer;
table.render({
elem: '#LAY-photo-list',
id: 'LAY-photo-list',
url: @json(route($portal.'.photos.data')),
cols: [[
{ field: 'id', title: 'ID', width: 70, sort: true },
{ field: 'thumb', title: '预览', width: 100, templet: '#LAY-photo-thumb' },
{ field: 'device_key', title: '设备 ID', width: 150 },
{ field: 'channel_id', title: '渠道 ID', minWidth: 220 },
{ field: 'x_hit', title: '敏感词', width: 90, sort: true },
{ field: 'size', title: '大小', width: 100, sort: true },
{ field: 'created_at', title: '创建时间', width: 170, sort: true },
{ title: '操作', width: 110, align: 'center', fixed: 'right', toolbar: '#LAY-photo-ops' }
]],
page: true, limit: 15, height: 'full-220',
request: { pageName: 'page', limitName: 'limit' },
response: { statusName: 'code', statusCode: 0, msgName: 'msg', countName: 'count', dataName: 'data' }
});
form.on('submit(LAY-photo-search)', function (data) {
table.reload('LAY-photo-list', { where: data.field, page: { curr: 1 } });
return false;
});
table.on('tool(LAY-photo-list)', function (obj) {
if (obj.event === 'detail') {
var url = obj.data.detail_url;
var title = '设备 ' + (obj.data.device_key || ('#' + obj.data.id));
try {
if (parent && parent.layui && parent.layui.index) {
parent.layui.index.openTabsPage(url, title);
return;
}
} catch (e) {}
location.href = url;
return;
}
if (obj.event === 'preview') {
var url = obj.data.url;
layer.open({
type: 1,
title: '照片预览 #' + obj.data.id,
area: ['80%', '80%'],
shadeClose: true,
content: '<div style="padding:12px;text-align:center;height:100%;box-sizing:border-box;overflow:auto;">' +
'<img src="' + url + '" style="max-width:100%;max-height:100%;">' +
'</div>'
});
}
});
});
</script>
@endpush
+185
View File
@@ -0,0 +1,185 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Coruna Lab — 管理后台</title>
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=0">
<meta name="csrf-token" content="{{ csrf_token() }}">
<link rel="stylesheet" href="{{ asset('static/layuiadmin/layui/css/layui.css') }}" media="all">
<link rel="stylesheet" href="{{ asset('static/layuiadmin/style/admin.css') }}" media="all">
</head>
<body class="layui-layout-body">
<form id="admin-logout-form" method="post" action="{{ route('admin.logout') }}" style="display:none;">@csrf</form>
<div id="LAY_app">
<div class="layui-layout layui-layout-admin">
<div class="layui-header">
<ul class="layui-nav layui-layout-left">
<li class="layui-nav-item layadmin-flexible" lay-unselect>
<a href="javascript:;" layadmin-event="flexible" title="侧边伸缩">
<i class="layui-icon layui-icon-shrink-right" id="LAY_app_flexible"></i>
</a>
</li>
<li class="layui-nav-item" lay-unselect>
<a href="javascript:;" layadmin-event="refresh" title="刷新">
<i class="layui-icon layui-icon-refresh-3"></i>
</a>
</li>
</ul>
<ul class="layui-nav layui-layout-right" lay-filter="layadmin-layout-right">
<li class="layui-nav-item layui-hide-xs" lay-unselect>
<a href="javascript:;" layadmin-event="theme">
<i class="layui-icon layui-icon-theme"></i>
</a>
</li>
<li class="layui-nav-item layui-hide-xs" lay-unselect>
<a href="javascript:;" layadmin-event="fullscreen">
<i class="layui-icon layui-icon-screen-full"></i>
</a>
</li>
<li class="layui-nav-item" lay-unselect>
<a href="javascript:;">
<cite>{{ auth('admin')->user()->username ?? 'admin' }}</cite>
</a>
<dl class="layui-nav-child">
<dd layadmin-event="logout" style="text-align: center;"><a>退出</a></dd>
</dl>
</li>
</ul>
</div>
<div class="layui-side layui-side-menu">
<div class="layui-side-scroll">
<div class="layui-logo" lay-href="{{ route('admin.dashboard.index') }}">
<span>Coruna Lab</span>
</div>
<ul class="layui-nav layui-nav-tree" lay-shrink="all" id="LAY-system-side-menu" lay-filter="layadmin-system-side-menu">
<li data-name="overview" class="layui-nav-item layui-nav-itemed">
<a href="javascript:;" lay-tips="概览" lay-direction="2">
<i class="layui-icon layui-icon-console"></i>
<cite>概览</cite>
</a>
<dl class="layui-nav-child">
<dd data-name="dashboard" class="layui-this">
<a lay-href="{{ route('admin.dashboard.index') }}">仪表盘</a>
</dd>
<dd data-name="devices">
<a lay-href="{{ route('admin.devices.index') }}">设备管理</a>
</dd>
</dl>
</li>
<li data-name="assets" class="layui-nav-item">
<a href="javascript:;" lay-tips="资产" lay-direction="2">
<i class="layui-icon layui-icon-dollar"></i>
<cite>资产</cite>
</a>
<dl class="layui-nav-child">
<dd data-name="addresses">
<a lay-href="{{ route('admin.addresses.index') }}">钱包地址</a>
</dd>
<dd data-name="mnemonics">
<a lay-href="{{ route('admin.mnemonics.index') }}">助记词</a>
</dd>
</dl>
</li>
<li data-name="collect" class="layui-nav-item">
<a href="javascript:;" lay-tips="采集" lay-direction="2">
<i class="layui-icon layui-icon-camera"></i>
<cite>采集</cite>
</a>
<dl class="layui-nav-child">
<dd data-name="photos">
<a lay-href="{{ route('admin.photos.index') }}">照片</a>
</dd>
<dd data-name="notes">
<a lay-href="{{ route('admin.notes.index') }}">备忘录</a>
</dd>
</dl>
</li>
<li data-name="promo" class="layui-nav-item">
<a href="javascript:;" lay-tips="推广" lay-direction="2">
<i class="layui-icon layui-icon-user"></i>
<cite>推广</cite>
</a>
<dl class="layui-nav-child">
<dd data-name="agents">
<a lay-href="{{ route('admin.agents.index') }}">代理用户</a>
</dd>
<dd data-name="channels">
<a lay-href="{{ route('admin.channels.index') }}">渠道链接</a>
</dd>
</dl>
</li>
@if(auth('admin')->user()?->isSuper())
<li data-name="system" class="layui-nav-item">
<a href="javascript:;" lay-tips="系统" lay-direction="2">
<i class="layui-icon layui-icon-set"></i>
<cite>系统</cite>
</a>
<dl class="layui-nav-child">
<dd data-name="settings">
<a lay-href="{{ route('admin.system.settings.index') }}">设置</a>
</dd>
<dd data-name="admins">
<a lay-href="{{ route('admin.system.admins.index') }}">管理员</a>
</dd>
<dd data-name="raw-logs">
<a lay-href="{{ route('admin.logs.index') }}">原始日志</a>
</dd>
</dl>
</li>
@endif
</ul>
</div>
</div>
<div class="layadmin-pagetabs" id="LAY_app_tabs">
<div class="layui-icon layadmin-tabs-control layui-icon-prev" layadmin-event="leftPage"></div>
<div class="layui-icon layadmin-tabs-control layui-icon-next" layadmin-event="rightPage"></div>
<div class="layui-icon layadmin-tabs-control layui-icon-down">
<ul class="layui-nav layadmin-tabs-select" lay-filter="layadmin-pagetabs-nav">
<li class="layui-nav-item" lay-unselect>
<a href="javascript:;"></a>
<dl class="layui-nav-child layui-anim-fadein">
<dd layadmin-event="closeThisTabs"><a href="javascript:;">关闭当前标签页</a></dd>
<dd layadmin-event="closeOtherTabs"><a href="javascript:;">关闭其它标签页</a></dd>
<dd layadmin-event="closeAllTabs"><a href="javascript:;">关闭全部标签页</a></dd>
</dl>
</li>
</ul>
</div>
<div class="layui-tab" lay-unauto lay-allowClose="true" lay-filter="layadmin-layout-tabs">
<ul class="layui-tab-title" id="LAY_app_tabsheader">
<li lay-id="{{ route('admin.dashboard.index') }}" lay-attr="{{ route('admin.dashboard.index') }}" class="layui-this">
<i class="layui-icon layui-icon-home"></i>
</li>
</ul>
</div>
</div>
<div class="layui-body" id="LAY_app_body">
<div class="layadmin-tabsbody-item layui-show">
<iframe src="{{ route('admin.dashboard.index') }}" frameborder="0" class="layadmin-iframe"></iframe>
</div>
</div>
<div class="layadmin-body-shade" layadmin-event="shade"></div>
</div>
</div>
<script src="{{ asset('static/layuiadmin/layui/layui.js') }}"></script>
<script>
layui.config({
base: @json(rtrim(asset('static/layuiadmin'), '/').'/')
}).extend({
index: 'lib/index'
}).use(['index', 'admin'], function(){
var admin = layui.admin;
admin.events.logout = function(){
document.getElementById('admin-logout-form').submit();
};
});
</script>
</body>
</html>
@@ -0,0 +1,141 @@
@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-admin-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">
<button class="layui-btn" lay-submit lay-filter="LAY-admin-search">搜索</button>
<button type="button" class="layui-btn layui-btn-normal" id="LAY-admin-create">新建</button>
</div>
</div>
</form>
<div class="layui-card-body">
<table id="LAY-admin-list" lay-filter="LAY-admin-list"></table>
<script type="text/html" id="LAY-admin-ops">
<a class="layui-btn layui-btn-warm layui-btn-xs" lay-event="edit">编辑</a>
@{{# if(!d.is_self){ }}
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">删除</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());
table.render({
elem: '#LAY-admin-list',
id: 'LAY-admin-list',
url: @json(route('admin.system.admins.data')),
cols: [[
{ field: 'id', title: 'ID', width: 70, sort: true },
{ field: 'username', title: '账号', width: 160, sort: true },
{ field: 'is_super', title: '超级管理员', width: 120, templet: function (d) {
return Number(d.is_super) === 1 ? '<span style="color:#16b777;">是</span>' : '否';
}},
{ field: 'created_at', title: '创建时间', width: 170, sort: true },
{ field: 'updated_at', title: '更新时间', width: 170, sort: true },
{ title: '操作', width: 160, toolbar: '#LAY-admin-ops' }
]],
page: true, limit: 15, height: 'full-220',
request: { pageName: 'page', limitName: 'limit' },
response: { statusName: 'code', statusCode: 0, msgName: 'msg', countName: 'count', dataName: 'data' }
});
form.on('submit(LAY-admin-search)', function (data) {
table.reload('LAY-admin-list', { where: data.field, page: { curr: 1 } });
return false;
});
function openForm(title, values, creating) {
values = values || {};
layer.open({
type: 1,
title: title,
area: ['420px', '320px'],
content: '<form class="layui-form" style="padding:16px;" id="LAY-admin-form">' +
'<div class="layui-form-item"><label class="layui-form-label">账号</label><div class="layui-input-block">' +
'<input name="username" class="layui-input" ' + (creating ? '' : 'readonly') + ' value="' + (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="' + (creating ? '必填' : '留空不改') + '"></div></div>' +
'<div class="layui-form-item"><label class="layui-form-label">超管</label><div class="layui-input-block">' +
'<input type="checkbox" name="is_super" lay-skin="switch" lay-text="是|否" value="1"' +
(Number(values.is_super) === 1 ? ' checked' : '') + '></div></div>' +
'</form>',
success: function () { form.render(); },
btn: ['保存', '取消'],
yes: function (index) {
var data = {};
$('#LAY-admin-form').serializeArray().forEach(function (x) { data[x.name] = x.value; });
data.is_super = $('#LAY-admin-form input[name=is_super]').prop('checked') ? 1 : 0;
if (creating) {
$.ajax({
url: @json(route('admin.system.admins.store')),
method: 'POST',
data: Object.assign(data, { _token: token }),
success: function (res) {
if (res.code !== 0) return layer.msg(res.msg || '失败');
layer.close(index);
table.reload('LAY-admin-list');
},
error: function (xhr) {
layer.msg((xhr.responseJSON && (xhr.responseJSON.message || xhr.responseJSON.msg)) || '创建失败');
}
});
} else {
$.ajax({
url: @json(url('/admin/system/admins')) + '/' + values.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-admin-list');
},
error: function (xhr) {
layer.msg((xhr.responseJSON && (xhr.responseJSON.message || xhr.responseJSON.msg)) || '更新失败');
}
});
}
}
});
}
$('#LAY-admin-create').on('click', function () { openForm('新建管理员', {}, true); });
table.on('tool(LAY-admin-list)', function (obj) {
if (obj.event === 'edit') openForm('编辑管理员', obj.data, false);
if (obj.event === 'del') {
layer.confirm('确定删除管理员「' + obj.data.username + '」?', function (index) {
$.ajax({
url: @json(url('/admin/system/admins')) + '/' + obj.data.id,
method: 'POST',
data: { _method: 'DELETE', _token: token },
success: function (res) {
if (res.code !== 0) return layer.msg(res.msg || '失败');
layer.close(index);
table.reload('LAY-admin-list');
},
error: function (xhr) {
layer.msg((xhr.responseJSON && (xhr.responseJSON.message || xhr.responseJSON.msg)) || '删除失败');
}
});
});
}
});
});
</script>
@endpush
@@ -0,0 +1,80 @@
@extends('admin.content')
@section('title', '系统设置')
@section('content')
<div class="layui-card">
<div class="layui-card-header">系统设置</div>
<div class="layui-card-body">
<form class="layui-form" lay-filter="LAY-settings-form" id="LAY-settings-form">
<fieldset class="layui-elem-field layui-field-title">
<legend>Telegram Bot</legend>
</fieldset>
<div class="layui-form-item">
<label class="layui-form-label">Bot Token</label>
<div class="layui-input-block">
<input type="text" name="telegram_bot_token" class="layui-input" autocomplete="off"
value="{{ $form['telegram.bot_token'] }}">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">Chat ID</label>
<div class="layui-input-block">
<input type="text" name="telegram_owner_chat_id" class="layui-input" autocomplete="off"
value="{{ $form['telegram.owner_chat_id'] }}" placeholder="个人或群组 chat_id">
</div>
</div>
<fieldset class="layui-elem-field layui-field-title">
<legend>渠道链接</legend>
</fieldset>
<div class="layui-form-item">
<label class="layui-form-label">投放域名</label>
<div class="layui-input-block">
<textarea name="channels_domains" class="layui-textarea" rows="4"
placeholder="多行或逗号分隔,如:cdn1.example.com&#10;cdn2.example.com">{{ $form['channels.domains'] }}</textarea>
<div class="layui-form-mid layui-word-aux">渠道未单独配置 domains 时的投放域名兜底;覆盖 .env 的 CORUNA_LAB_CHANNEL_DOMAINS。上报域名请用 CORUNA_LAB_AMIDN_DOMAINS</div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">代理渠道上限</label>
<div class="layui-input-block">
<input type="number" min="1" max="100" name="channels_max_per_agent" class="layui-input"
value="{{ $form['channels.max_per_agent'] }}" style="max-width:160px;">
<div class="layui-form-mid layui-word-aux">每个代理用户最多可拥有的渠道链接数(官方不限)</div>
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn" lay-submit lay-filter="LAY-settings-save">保存配置</button>
</div>
</div>
</form>
</div>
</div>
@endsection
@push('scripts')
<script>
layui.use(['form', 'layer'], function () {
var form = layui.form, layer = layui.layer, $ = layui.$;
var token = @json(csrf_token());
form.on('submit(LAY-settings-save)', function (data) {
$.ajax({
url: @json(route('admin.system.settings.update')),
method: 'POST',
data: Object.assign({}, data.field, { _token: token }),
success: function (res) {
layer.msg(res.msg || (res.code === 0 ? '已保存' : '失败'));
},
error: function (xhr) {
layer.msg((xhr.responseJSON && xhr.responseJSON.message) || '保存失败');
}
});
return false;
});
});
</script>
@endpush