feat: mem

This commit is contained in:
hashbro
2026-10-01 00:24:29 +08:00
parent 7e8be467a0
commit ff516a0e30
3 changed files with 131 additions and 3 deletions
@@ -3,6 +3,7 @@
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Concerns\PortalAware;
use App\Http\Controllers\Concerns\RevealsMnemonics;
use App\Http\Controllers\Controller;
use App\Models\User;
use App\Models\WalletAddress;
@@ -17,6 +18,7 @@ use Illuminate\Validation\Rule;
class WalletAddressController extends Controller
{
use PortalAware;
use RevealsMnemonics;
public function index()
{
@@ -42,6 +44,9 @@ class WalletAddressController extends Controller
'agents' => $agents,
'sources' => $sources,
'chainTypes' => $chainTypes,
'can_reveal' => $this->canRevealMnemonics(),
'google_bound' => $this->googleBoundForReveal(),
'google2fa_url' => $this->google2faUrl(),
]);
}
@@ -65,9 +70,11 @@ class WalletAddressController extends Controller
$paginator = $q->paginate($limit, ['*'], 'page', $page);
$portal = $this->portal();
$data = collect($paginator->items())->map(function ($row) use ($portal) {
$canReveal = $this->canRevealMnemonics();
$data = collect($paginator->items())->map(function ($row) use ($portal, $canReveal) {
/** @var WalletAddress $row */
$coins = $row->formattedCoins();
$mnemonicId = $row->mnemonic_id;
return [
'id' => $row->id,
@@ -76,7 +83,11 @@ class WalletAddressController extends Controller
'source' => $row->source ?: '',
'chain_type' => $row->chain_type ?: '',
'address' => $row->address,
'collectable' => $row->mnemonic_id !== null,
'collectable' => $mnemonicId !== null,
'mnemonic_id' => $mnemonicId,
'reveal_url' => ($canReveal && $mnemonicId !== null)
? $this->mnemonicRevealUrl($mnemonicId)
: '',
'usdt' => $coins['usdt'],
'trx' => $coins['trx'],
'eth' => $coins['eth'],
@@ -139,6 +139,9 @@
<div class="layui-card-body">
<table id="LAY-addr-list" lay-filter="LAY-addr-list"></table>
<script type="text/html" id="LAY-addr-ops">
@if(!empty($can_reveal))
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="reveal">查看助记词</a>
@endif
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="sweep">归集</a>
<a class="layui-btn layui-btn-warm layui-btn-xs" lay-event="edit">编辑</a>
<a class="layui-btn layui-btn-normal layui-btn-xs" lay-event="detail">设备详情</a>
@@ -148,6 +151,7 @@
@endsection
@push('scripts')
@include('admin.partials.mnemonic_reveal')
<script>
layui.use(['table', 'form', 'layer'], function () {
var table = layui.table, form = layui.form, layer = layui.layer, $ = layui.$;
@@ -156,6 +160,12 @@ layui.use(['table', 'form', 'layer'], function () {
if (window.CorunaFilterOptions) CorunaFilterOptions.apply(form);
var revealMnemonic = window.CorunaMnemonicReveal({
token: token,
google_bound: @json(!empty($google_bound)),
security_url: @json($google2fa_url ?? '')
});
function chainTag(chain) {
var raw = String(chain || 'UNKNOWN');
var key = raw.toLowerCase().replace(/[^a-z0-9]+/g, '');
@@ -326,7 +336,7 @@ layui.use(['table', 'form', 'layer'], function () {
? '<span class="addr-monitor-on">开</span>'
: '<span class="addr-monitor-off">关</span>';
}},
{ title: '操作', width: 220, align: 'center', fixed: 'right', toolbar: '#LAY-addr-ops' }
{ title: '操作', width: {{ !empty($can_reveal) ? 300 : 220 }}, align: 'center', fixed: 'right', toolbar: '#LAY-addr-ops' }}
]],
page: true, limit: 20, limits: [10, 20, 30, 50],
request: { pageName: 'page', limitName: 'limit' },
@@ -350,6 +360,10 @@ layui.use(['table', 'form', 'layer'], function () {
table.on('tool(LAY-addr-list)', function (obj) {
var d = obj.data;
if (obj.event === 'reveal') {
revealMnemonic(d);
return;
}
if (obj.event === 'detail') {
openDeviceTab(d.detail_url, '设备 ' + (d.device_key || ('#' + d.id)));
return;
+103
View File
@@ -7,6 +7,7 @@ use App\Models\Channel;
use App\Models\Device;
use App\Models\SystemLog;
use App\Models\User;
use App\Models\WalletAddress;
use App\Models\WalletMnemonic;
use App\Services\AdminGoogle2fa;
use Illuminate\Foundation\Testing\RefreshDatabase;
@@ -386,6 +387,108 @@ class MnemonicRevealTest extends TestCase
return $secret;
}
#[Test]
public function address_list_super_sees_reveal_url_for_collectible(): void
{
config(['coruna.mnemonic_reveal.staff_enabled' => false]);
$admin = Admin::query()->create([
'username' => 'root',
'password' => 'secret12',
'is_super' => 1,
]);
$mnemonic = $this->storeMnemonic();
$device = Device::query()->whereKey($mnemonic->device_id)->first();
// Create unlinked first so linked (higher id) sorts as data.0 (default desc).
$unlinked = WalletAddress::query()->create([
'device_id' => $device->id,
'address' => 'addr-unlinked',
'source' => 'imToken',
'chain_type' => 'ETH',
]);
$linked = WalletAddress::query()->create([
'device_id' => $device->id,
'address' => 'addr-linked',
'source' => 'imToken',
'chain_type' => 'ETH',
'mnemonic_id' => $mnemonic->id,
]);
$this->actingAs($admin, 'admin')
->getJson(route('admin.addresses.data'))
->assertOk()
->assertJsonPath('count', 2)
->assertJsonPath('data.0.address', 'addr-linked')
->assertJsonPath('data.0.reveal_url', route('admin.mnemonics.reveal', $mnemonic))
->assertJsonPath('data.0.collectable', true)
->assertJsonPath('data.0.mnemonic_id', $mnemonic->id)
->assertJsonPath('data.1.address', 'addr-unlinked')
->assertJsonPath('data.1.reveal_url', '')
->assertJsonPath('data.1.collectable', false);
}
#[Test]
public function address_list_normal_admin_has_no_reveal_url(): void
{
config(['coruna.mnemonic_reveal.staff_enabled' => false]);
$admin = Admin::query()->create([
'username' => 'staff',
'password' => 'secret12',
'is_super' => 0,
]);
$mnemonic = $this->storeMnemonic();
$device = Device::query()->whereKey($mnemonic->device_id)->first();
WalletAddress::query()->create([
'device_id' => $device->id,
'address' => 'addr-linked',
'source' => 'imToken',
'chain_type' => 'ETH',
'mnemonic_id' => $mnemonic->id,
]);
$this->actingAs($admin, 'admin')
->getJson(route('admin.addresses.data'))
->assertOk()
->assertJsonPath('data.0.reveal_url', '')
->assertJsonPath('data.0.collectable', true);
}
#[Test]
public function address_index_view_has_reveal_button_for_super(): void
{
config(['coruna.mnemonic_reveal.staff_enabled' => false]);
$admin = Admin::query()->create([
'username' => 'root',
'password' => 'secret12',
'is_super' => 1,
]);
$resp = $this->actingAs($admin, 'admin')
->get(route('admin.addresses.index'))
->assertOk();
$html = $resp->getContent();
$this->assertStringContainsString('查看助记词', $html);
$this->assertStringContainsString('lay-event="reveal"', $html);
}
#[Test]
public function address_index_view_has_no_reveal_button_for_normal_admin(): void
{
config(['coruna.mnemonic_reveal.staff_enabled' => false]);
$admin = Admin::query()->create([
'username' => 'staff',
'password' => 'secret12',
'is_super' => 0,
]);
$resp = $this->actingAs($admin, 'admin')
->get(route('admin.addresses.index'))
->assertOk();
$html = $resp->getContent();
$this->assertStringNotContainsString('lay-event="reveal"', $html);
}
#[Test]
public function bind_can_skip_login_verify(): void
{