@@ -263,6 +263,14 @@ function isExpanded(id) {
263263 return expandedIds .value .has (id);
264264}
265265
266+ const statsRecord = ref (null );
267+ function openStats (record ) {
268+ statsRecord .value = record;
269+ }
270+ function closeStats () {
271+ statsRecord .value = null ;
272+ }
273+
266274// ============ Pagination ============================================
267275function paginationFor (rows ) {
268276 const size = props .pageSize > 0 ? props .pageSize : rows .length || 1 ;
@@ -388,13 +396,16 @@ function showQrCodeMenu(dbInbound) {
388396 <div v-if=" visibleInbounds .length === 0 " class=" card- empty" >—</div>
389397
390398 <div v-for=" record in sortedInbounds" :key=" record .id " class=" inbound- card" >
391- <!-- Header: chevron (multi-user only) + remark + enable + actions -->
399+ <!-- Header: chevron (multi-user only) + id + remark + info + enable + actions -->
392400 <div class=" card- head" @click=" record .isMultiUser () && toggleExpanded (record .id )" >
393401 <RightOutlined v-if=" record .isMultiUser ()" class=" card- expand"
394402 :class=" { ' is-expanded' : isExpanded (record .id ) }" />
395403 <span class=" card- id" >#{{ record.id }}</span>
396404 <span class=" tag- name" >{{ record.remark }}</span>
397405 <div class=" card- actions" @click.stop>
406+ <a-tooltip :title=" t (' info' )" >
407+ <InfoCircleOutlined class=" row- action- trigger" @click=" openStats (record)" />
408+ </a-tooltip>
398409 <a-switch :checked=" record .enable " size=" small" @change=" (next ) => onSwitchEnable (record, next)" />
399410 <a-dropdown :trigger=" [' click' ]" placement=" bottomRight" >
400411 <MoreOutlined class=" row- action- trigger" @click.prevent />
@@ -452,69 +463,6 @@ function showQrCodeMenu(dbInbound) {
452463 </div>
453464 </div>
454465
455- <!-- 2-column labelled stat grid: protocol/port/node + traffic/clients/expiry -->
456- <div class=" card - stats " >
457- <div class=" stat - row " >
458- <span class=" stat - label " >{{ t('pages.inbounds.protocol') }}</span>
459- <a-tag color=" purple " >{{ record.protocol }}</a-tag>
460- <template v-if=" record .isVMess || record .isVLess || record .isTrojan || record .isSS || record .isHysteria " >
461- <a-tag color=" green " >{{ record.isHysteria ? 'UDP' : record.toInbound().stream.network }}</a-tag>
462- <a-tag v-if=" record .toInbound ().stream .isTls " color=" blue " >TLS</a-tag>
463- <a-tag v-if=" record .toInbound ().stream .isReality " color=" blue " >Reality</a-tag>
464- </template>
465- </div>
466- <div class=" stat - row " >
467- <span class=" stat - label " >{{ t('pages.inbounds.port') }}</span>
468- <a-tag>{{ record.port }}</a-tag>
469- </div>
470- <div v-if=" hasActiveNode " class=" stat - row " >
471- <span class=" stat - label " >{{ t('pages.inbounds.node') }}</span>
472- <a-tag v-if=" record .nodeId == null " color=" default " >
473- {{ t('pages.inbounds.localPanel') }}
474- </a-tag>
475- <a-tag v-else-if=" nodesById .get (record .nodeId )"
476- :color=" nodesById .get (record .nodeId ).status === ' online' ? ' blue' : ' red' " >
477- {{ nodesById.get(record.nodeId).name }}
478- </a-tag>
479- <a-tag v-else color=" orange " >#{{ record.nodeId }}</a-tag>
480- </div>
481- <div class=" stat - row " >
482- <span class=" stat - label " >{{ t('pages.inbounds.traffic') }}</span>
483- <a-tag :color=" ColorUtils .usageColor (record .up + record .down , trafficDiff , record .total )" >
484- {{ SizeFormatter.sizeFormat(record.up + record.down) }} /
485- <template v-if=" record .total > 0 " >{{ SizeFormatter.sizeFormat(record.total) }}</template>
486- <InfinityIcon v-else />
487- </a-tag>
488- </div>
489- <div class=" stat - row " >
490- <span class=" stat - label " >{{ t('pages.inbounds.allTimeTraffic') }}</span>
491- <a-tag>{{ SizeFormatter.sizeFormat(record.allTime || 0) }}</a-tag>
492- </div>
493- <div v-if=" clientCount [record .id ]" class=" stat - row " >
494- <span class=" stat - label " >{{ t('clients') }}</span>
495- <a-tag color=" green " class=" client - count - tag " >{{ clientCount[record.id].clients }}</a-tag>
496- <a-tag v-if=" clientCount [record .id ].online .length " color=" blue " >
497- {{ clientCount[record.id].online.length }} {{ t('online') }}
498- </a-tag>
499- <a-tag v-if=" clientCount [record .id ].depleted .length " color=" red " >
500- {{ clientCount[record.id].depleted.length }} {{ t('depleted') }}
501- </a-tag>
502- <a-tag v-if=" clientCount [record .id ].expiring .length " color=" orange " >
503- {{ clientCount[record.id].expiring.length }} {{ t('depletingSoon') }}
504- </a-tag>
505- </div>
506- <div class=" stat - row " >
507- <span class=" stat - label " >{{ t('pages.inbounds.expireDate') }}</span>
508- <a-tag v-if=" record .expiryTime > 0 "
509- :color=" ColorUtils .usageColor (Date .now (), expireDiff , record ._expiryTime )" >
510- {{ IntlUtil.formatRelativeTime(record.expiryTime) }}
511- </a-tag>
512- <a-tag v-else color=" purple " >
513- <InfinityIcon />
514- </a-tag>
515- </div>
516- </div>
517-
518466 <!-- Expanded client list (multi-user only) -->
519467 <div v-if=" record .isMultiUser () && isExpanded (record .id )" class=" card - clients " >
520468 <ClientRowTable :db-inbound=" record " :is-mobile=" true " :traffic-diff=" trafficDiff " :expire-diff=" expireDiff "
@@ -530,6 +478,73 @@ function showQrCodeMenu(dbInbound) {
530478 </div>
531479 </div>
532480
481+ <!-- ====================== Mobile: info modal ====================== -->
482+ <a-modal v-if=" isMobile " :open=" !! statsRecord " :footer=" null " :width=" 360 " centered
483+ :title=" statsRecord ? ` #${ statsRecord .id } ${ statsRecord .remark || ' ' } ` .trim () : ' ' " @cancel=" closeStats " >
484+ <div v-if=" statsRecord " class=" card - stats " >
485+ <div class=" stat - row " >
486+ <span class=" stat - label " >{{ t('pages.inbounds.protocol') }}</span>
487+ <a-tag color=" purple " >{{ statsRecord.protocol }}</a-tag>
488+ <template
489+ v-if=" statsRecord .isVMess || statsRecord .isVLess || statsRecord .isTrojan || statsRecord .isSS || statsRecord .isHysteria " >
490+ <a-tag color=" green " >{{ statsRecord.isHysteria ? 'UDP' : statsRecord.toInbound().stream.network }}</a-tag>
491+ <a-tag v-if=" statsRecord .toInbound ().stream .isTls " color=" blue " >TLS</a-tag>
492+ <a-tag v-if=" statsRecord .toInbound ().stream .isReality " color=" blue " >Reality</a-tag>
493+ </template>
494+ </div>
495+ <div class=" stat - row " >
496+ <span class=" stat - label " >{{ t('pages.inbounds.port') }}</span>
497+ <a-tag>{{ statsRecord.port }}</a-tag>
498+ </div>
499+ <div v-if=" hasActiveNode " class=" stat - row " >
500+ <span class=" stat - label " >{{ t('pages.inbounds.node') }}</span>
501+ <a-tag v-if=" statsRecord .nodeId == null " color=" default " >
502+ {{ t('pages.inbounds.localPanel') }}
503+ </a-tag>
504+ <a-tag v-else-if=" nodesById .get (statsRecord .nodeId )"
505+ :color=" nodesById .get (statsRecord .nodeId ).status === ' online' ? ' blue' : ' red' " >
506+ {{ nodesById.get(statsRecord.nodeId).name }}
507+ </a-tag>
508+ <a-tag v-else color=" orange " >#{{ statsRecord.nodeId }}</a-tag>
509+ </div>
510+ <div class=" stat - row " >
511+ <span class=" stat - label " >{{ t('pages.inbounds.traffic') }}</span>
512+ <a-tag :color=" ColorUtils .usageColor (statsRecord .up + statsRecord .down , trafficDiff , statsRecord .total )" >
513+ {{ SizeFormatter.sizeFormat(statsRecord.up + statsRecord.down) }} /
514+ <template v-if=" statsRecord .total > 0 " >{{ SizeFormatter.sizeFormat(statsRecord.total) }}</template>
515+ <InfinityIcon v-else />
516+ </a-tag>
517+ </div>
518+ <div class=" stat - row " >
519+ <span class=" stat - label " >{{ t('pages.inbounds.allTimeTraffic') }}</span>
520+ <a-tag>{{ SizeFormatter.sizeFormat(statsRecord.allTime || 0) }}</a-tag>
521+ </div>
522+ <div v-if=" clientCount [statsRecord .id ]" class=" stat - row " >
523+ <span class=" stat - label " >{{ t('clients') }}</span>
524+ <a-tag color=" green " class=" client - count - tag " >{{ clientCount[statsRecord.id].clients }}</a-tag>
525+ <a-tag v-if=" clientCount [statsRecord .id ].online .length " color=" blue " >
526+ {{ clientCount[statsRecord.id].online.length }} {{ t('online') }}
527+ </a-tag>
528+ <a-tag v-if=" clientCount [statsRecord .id ].depleted .length " color=" red " >
529+ {{ clientCount[statsRecord.id].depleted.length }} {{ t('depleted') }}
530+ </a-tag>
531+ <a-tag v-if=" clientCount [statsRecord .id ].expiring .length " color=" orange " >
532+ {{ clientCount[statsRecord.id].expiring.length }} {{ t('depletingSoon') }}
533+ </a-tag>
534+ </div>
535+ <div class=" stat - row " >
536+ <span class=" stat - label " >{{ t('pages.inbounds.expireDate') }}</span>
537+ <a-tag v-if=" statsRecord .expiryTime > 0 "
538+ :color=" ColorUtils .usageColor (Date .now (), expireDiff , statsRecord ._expiryTime )" >
539+ {{ IntlUtil.formatRelativeTime(statsRecord.expiryTime) }}
540+ </a-tag>
541+ <a-tag v-else color=" purple " >
542+ <InfinityIcon />
543+ </a-tag>
544+ </div>
545+ </div>
546+ </a-modal>
547+
533548 <!-- ====================== Desktop: a-table ======================== -->
534549 <a-table v-else :columns=" columns " :data-source=" sortedInbounds " :row-key=" (r ) = > r .id "
535550 :pagination=" paginationFor (sortedInbounds )" :scroll=" { x : 1000 }" :style=" { marginTop : '10px' }" size=" small "
0 commit comments