账号列表支持刷新状态/查看系统通知/管理在线设备

This commit is contained in:
meoacgx committed 2025-12-19 14:29:57 +08:00
1 parent ccbb2aebed
commit 00c2275dcf
4 files changed
+405

No files matched your search

@@ -0,0 +1,192 @@
@namespace TelegramPanel.Web.Components.Dialogs
@inject AccountTelegramToolsService AccountTelegramTools
@inject IDialogService DialogService
@inject ISnackbar Snackbar
<MudDialog>
<DialogContent>
<MudStack Spacing="2">
<MudText Typo="Typo.subtitle1">账号:@Phone(ID:@AccountId)</MudText>
<MudAlert Severity="Severity.Info" Variant="Variant.Outlined" Dense="true">
这里显示账号当前在线设备/会话,可对“非当前设备”执行踢出登录。
</MudAlert>
<MudStack Row="true" Spacing="2">
<MudButton Variant="Variant.Filled" Color="Color.Primary" StartIcon="@Icons.Material.Filled.Refresh"
Disabled="@loading" OnClick="LoadDevices">
刷新
</MudButton>
<MudButton Variant="Variant.Outlined" Color="Color.Error" StartIcon="@Icons.Material.Filled.Logout"
Disabled="@loading" OnClick="KickAllOthers">
踢出所有其他设备
</MudButton>
</MudStack>
@if (loading)
{
<MudProgressLinear Indeterminate="true" />
}
else if (devices.Count == 0)
{
<MudText Typo="Typo.body2" Color="Color.Secondary">暂无设备信息</MudText>
}
else
{
<MudTable Items="@devices" Dense="true" Hover="true" Breakpoint="Breakpoint.Sm">
<HeaderContent>
<MudTh>当前</MudTh>
<MudTh>应用/设备</MudTh>
<MudTh>系统</MudTh>
<MudTh>IP/地区</MudTh>
<MudTh>最近活跃</MudTh>
<MudTh>操作</MudTh>
</HeaderContent>
<RowTemplate>
<MudTd DataLabel="当前">
<MudChip T="string" Size="Size.Small" Color="@(context.Current ? Color.Success : Color.Default)">
@(context.Current ? "是" : "否")
</MudChip>
</MudTd>
<MudTd DataLabel="应用/设备">
<MudText Typo="Typo.body2">@context.Title</MudText>
<MudText Typo="Typo.caption" Color="Color.Secondary">ApiId=@context.ApiId @context.AppVersion</MudText>
</MudTd>
<MudTd DataLabel="系统">
<MudText Typo="Typo.body2">@($"{context.Platform} {context.SystemVersion}".Trim())</MudText>
</MudTd>
<MudTd DataLabel="IP/地区">
<MudText Typo="Typo.body2">@context.Ip</MudText>
<MudText Typo="Typo.caption" Color="Color.Secondary">@($"{context.Country} {context.Region}".Trim())</MudText>
</MudTd>
<MudTd DataLabel="最近活跃">
<MudText Typo="Typo.body2">@(context.LastActiveAtUtc?.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss") ?? "-")</MudText>
</MudTd>
<MudTd DataLabel="操作">
<MudButton Variant="Variant.Text" Color="Color.Error" Disabled="@(context.Current || loading)"
OnClick="@(() => KickOne(context))">
踢出
</MudButton>
</MudTd>
</RowTemplate>
</MudTable>
}
</MudStack>
</DialogContent>
<DialogActions>
<MudButton Variant="Variant.Text" Color="Color.Default" OnClick="Close">关闭</MudButton>
</DialogActions>
</MudDialog>
@code
{
[CascadingParameter] private MudDialogInstance MudDialog { get; set; } = default!;
[Parameter] public int AccountId { get; set; }
[Parameter] public string Phone { get; set; } = "";
private bool loading;
private List<TelegramAuthorizationInfo> devices = new();
protected override async Task OnInitializedAsync()
{
await LoadDevices();
}
private async Task LoadDevices()
{
loading = true;
try
{
var list = await AccountTelegramTools.GetAuthorizationsAsync(AccountId);
devices = list.ToList();
}
catch (Exception ex)
{
Snackbar.Add($"加载设备失败:{ex.Message}", Severity.Error);
}
finally
{
loading = false;
}
}
private async Task KickOne(TelegramAuthorizationInfo auth)
{
if (auth.Current)
{
Snackbar.Add("不能踢出当前设备", Severity.Warning);
return;
}
bool? result = await DialogService.ShowMessageBox(
"确认踢出",
$"确定要踢出该设备登录吗?\n{auth.Title}\nIP:{auth.Ip}\n最近活跃:{auth.LastActiveAtUtc?.ToLocalTime():yyyy-MM-dd HH:mm:ss}",
yesText: "踢出", cancelText: "取消");
if (result != true)
return;
loading = true;
try
{
var ok = await AccountTelegramTools.KickAuthorizationAsync(AccountId, auth.Hash);
if (ok)
{
Snackbar.Add("已踢出该设备", Severity.Success);
await LoadDevices();
}
else
{
Snackbar.Add("踢出失败(返回 false)", Severity.Error);
}
}
catch (Exception ex)
{
Snackbar.Add($"踢出失败:{ex.Message}", Severity.Error);
}
finally
{
loading = false;
}
}
private async Task KickAllOthers()
{
bool? result = await DialogService.ShowMessageBox(
"确认踢出",
"确定要踢出所有其他设备吗?(当前设备会保留)",
yesText: "踢出", cancelText: "取消");
if (result != true)
return;
loading = true;
try
{
var ok = await AccountTelegramTools.KickAllOtherAuthorizationsAsync(AccountId);
if (ok)
{
Snackbar.Add("已踢出所有其他设备", Severity.Success);
await LoadDevices();
}
else
{
Snackbar.Add("踢出失败(返回 false)", Severity.Error);
}
}
catch (Exception ex)
{
Snackbar.Add($"踢出失败:{ex.Message}", Severity.Error);
}
finally
{
loading = false;
}
}
private void Close()
{
MudDialog.Close();
}
}
@@ -0,0 +1,90 @@
@namespace TelegramPanel.Web.Components.Dialogs
@inject AccountTelegramToolsService AccountTelegramTools
@inject ISnackbar Snackbar
<MudDialog>
<DialogContent>
<MudStack Spacing="2">
<MudText Typo="Typo.subtitle1">账号:@Phone(ID:@AccountId)</MudText>
<MudAlert Severity="Severity.Info" Variant="Variant.Outlined" Dense="true">
这里显示 Telegram 系统通知(通常用于接收登录验证码)。若列表为空,可能是该账号从未在 Telegram 内收到系统消息。
</MudAlert>
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
<MudButton Variant="Variant.Filled" Color="Color.Primary" StartIcon="@Icons.Material.Filled.Refresh"
Disabled="@loading" OnClick="LoadMessages">
刷新
</MudButton>
@if (lastLoadedAtUtc.HasValue)
{
<MudText Typo="Typo.caption" Color="Color.Default">最后刷新:@lastLoadedAtUtc.Value.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss")</MudText>
}
</MudStack>
@if (loading)
{
<MudProgressLinear Indeterminate="true" />
}
else if (messages.Count == 0)
{
<MudText Typo="Typo.body2" Color="Color.Secondary">暂无系统通知</MudText>
}
else
{
<MudList T="string" Dense="true">
@foreach (var m in messages)
{
<MudListItem T="string">
<MudText Typo="Typo.caption" Color="Color.Secondary">@((m.DateUtc ?? DateTime.MinValue).ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss"))</MudText>
<MudText Typo="Typo.body2" Style="white-space: pre-wrap;">@m.Text</MudText>
</MudListItem>
}
</MudList>
}
</MudStack>
</DialogContent>
<DialogActions>
<MudButton Variant="Variant.Text" Color="Color.Default" OnClick="Close">关闭</MudButton>
</DialogActions>
</MudDialog>
@code
{
[CascadingParameter] private MudDialogInstance MudDialog { get; set; } = default!;
[Parameter] public int AccountId { get; set; }
[Parameter] public string Phone { get; set; } = "";
private bool loading;
private DateTime? lastLoadedAtUtc;
private List<TelegramSystemMessage> messages = new();
protected override async Task OnInitializedAsync()
{
await LoadMessages();
}
private async Task LoadMessages()
{
loading = true;
try
{
var list = await AccountTelegramTools.GetLatestSystemMessagesAsync(AccountId, limit: 30);
messages = list.ToList();
lastLoadedAtUtc = DateTime.UtcNow;
}
catch (Exception ex)
{
Snackbar.Add($"加载系统通知失败:{ex.Message}", Severity.Error);
}
finally
{
loading = false;
}
}
private void Close()
{
MudDialog.Close();
}
}
@@ -1,6 +1,7 @@
@page "/accounts"
@inject AccountManagementService AccountManagement
@inject AccountCategoryManagementService CategoryManagement
@inject AccountTelegramToolsService AccountTelegramTools
@inject ISnackbar Snackbar
@inject IDialogService DialogService
@using TelegramPanel.Data.Entities
@@ -25,6 +26,10 @@
<MudTextField @bind-Value="searchString" Placeholder="搜索账号..." Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Search" IconSize="Size.Medium" Class="mt-0"
Immediate="true" />
<MudButton Variant="Variant.Outlined" Color="Color.Info" StartIcon="@Icons.Material.Filled.Sync"
Disabled="@loading" OnClick="BatchRefreshTelegramStatus" Class="ml-2">
批量刷新状态
</MudButton>
<MudButton Variant="Variant.Filled" Color="Color.Primary" StartIcon="@Icons.Material.Filled.Refresh"
OnClick="LoadAccounts" Class="ml-2">刷新</MudButton>
</ToolBarContent>
@@ -36,6 +41,7 @@
<MudTh>分类</MudTh>
<MudTh>频道/群组</MudTh>
<MudTh>状态</MudTh>
<MudTh>Telegram 状态</MudTh>
<MudTh>最后同步</MudTh>
<MudTh>操作</MudTh>
</HeaderContent>
@@ -64,10 +70,28 @@
@(context.IsActive ? "活跃" : "停用")
</MudChip>
</MudTd>
<MudTd DataLabel="Telegram 状态">
@if (telegramStatus.TryGetValue(context.Id, out var s))
{
<MudChip T="string" Size="Size.Small" Color="@(s.Ok ? Color.Success : Color.Error)" Title="@(s.Details ?? s.Summary)">
@s.Summary
</MudChip>
}
else
{
<MudChip T="string" Size="Size.Small" Color="Color.Default">未检测</MudChip>
}
</MudTd>
<MudTd DataLabel="最后同步">@context.LastSyncAt.ToString("yyyy-MM-dd HH:mm")</MudTd>
<MudTd DataLabel="操作">
<MudIconButton Icon="@Icons.Material.Filled.Info" Size="Size.Small" Color="Color.Info"
OnClick="@(() => ShowDetails(context))" Title="查看详情" />
<MudIconButton Icon="@Icons.Material.Filled.Sync" Size="Size.Small" Color="Color.Primary"
Disabled="@loading" OnClick="@(() => RefreshTelegramStatus(context))" Title="刷新 Telegram 状态" />
<MudIconButton Icon="@Icons.Material.Filled.MarkEmailUnread" Size="Size.Small" Color="Color.Info"
Disabled="@loading" OnClick="@(() => OpenSystemInbox(context))" Title="系统通知(验证码)" />
<MudIconButton Icon="@Icons.Material.Filled.Devices" Size="Size.Small" Color="Color.Info"
Disabled="@loading" OnClick="@(() => OpenDevices(context))" Title="在线设备" />
<MudIconButton Icon="@(context.IsActive ? Icons.Material.Filled.Stop : Icons.Material.Filled.PlayArrow)"
Size="Size.Small" Color="@(context.IsActive ? Color.Warning : Color.Success)"
OnClick="@(() => ToggleActive(context))"
@@ -92,6 +116,7 @@
private bool loading = true;
private string searchString = "";
private int filterCategoryId = 0;
private readonly Dictionary<int, TelegramAccountStatusResult> telegramStatus = new();
private IEnumerable<Account> filteredAccounts =>
accounts.Where(a =>
@@ -134,6 +159,10 @@
{
var accountList = await AccountManagement.GetAllAccountsAsync();
accounts = accountList.ToList();
// 刷新账号列表后,清理不存在账号的状态缓存
var ids = new HashSet<int>(accounts.Select(a => a.Id));
foreach (var key in telegramStatus.Keys.Where(k => !ids.Contains(k)).ToList())
telegramStatus.Remove(key);
await LoadCategories();
}
catch (Exception ex)
@@ -153,6 +182,99 @@
await DialogService.ShowMessageBox("账号详情", message, "关闭");
}
private async Task RefreshTelegramStatus(Account account)
{
try
{
loading = true;
var result = await AccountTelegramTools.RefreshAccountStatusAsync(account.Id);
telegramStatus[account.Id] = result;
// 资料刷新后重新加载(昵称/用户名/用户ID 可能更新)
await LoadAccounts();
Snackbar.Add($"账号 {account.Phone} 状态:{result.Summary}", result.Ok ? Severity.Success : Severity.Warning);
}
catch (Exception ex)
{
Snackbar.Add($"刷新失败:{ex.Message}", Severity.Error);
}
finally
{
loading = false;
}
}
private async Task BatchRefreshTelegramStatus()
{
if (accounts.Count == 0)
{
Snackbar.Add("暂无账号可刷新", Severity.Info);
return;
}
loading = true;
try
{
var targets = filteredAccounts.ToList();
foreach (var a in targets)
{
try
{
var result = await AccountTelegramTools.RefreshAccountStatusAsync(a.Id);
telegramStatus[a.Id] = result;
}
catch (Exception ex)
{
telegramStatus[a.Id] = new TelegramAccountStatusResult(
Ok: false,
Summary: "刷新失败",
Details: ex.Message,
CheckedAtUtc: DateTime.UtcNow);
}
StateHasChanged();
}
await LoadAccounts();
Snackbar.Add($"批量刷新完成:{targets.Count} 个账号", Severity.Success);
}
catch (Exception ex)
{
Snackbar.Add($"批量刷新失败:{ex.Message}", Severity.Error);
}
finally
{
loading = false;
}
}
private async Task OpenSystemInbox(Account account)
{
var parameters = new DialogParameters
{
["AccountId"] = account.Id,
["Phone"] = account.Phone
};
var options = new DialogOptions { CloseOnEscapeKey = true, MaxWidth = MaxWidth.Medium, FullWidth = true };
DialogService.Show<TelegramPanel.Web.Components.Dialogs.SystemInboxDialog>("系统通知(验证码)", parameters, options);
await Task.CompletedTask;
}
private async Task OpenDevices(Account account)
{
var parameters = new DialogParameters
{
["AccountId"] = account.Id,
["Phone"] = account.Phone
};
var options = new DialogOptions { CloseOnEscapeKey = true, MaxWidth = MaxWidth.Large, FullWidth = true };
DialogService.Show<TelegramPanel.Web.Components.Dialogs.OnlineDevicesDialog>("在线设备", parameters, options);
await Task.CompletedTask;
}
private async Task ToggleActive(Account account)
{
try
@@ -13,5 +13,6 @@
@using TelegramPanel.Core.Interfaces
@using TelegramPanel.Core.Services
@using TelegramPanel.Core.Services.Telegram
@using TelegramPanel.Core.Models
@using TelegramPanel.Web.Components
@using TelegramPanel.Web.Components.Layout