账号列表支持刷新状态/查看系统通知/管理在线设备
This commit is contained in:
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
|
||||
Reference in new issue
Block a user