Skip to content

Commit 500be23

Browse files
Merge branch 'SECTL:master' into master
2 parents 53dea9e + 748c693 commit 500be23

4 files changed

Lines changed: 140 additions & 6 deletions

File tree

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<img width="1800" height="766" alt="新版本" src="https://github.com/SECTL/SecRandom/blob/master/SecRandom/Assets/Banners/secrandom-banner-cn.png" />
2+
3+
> [!caution]
4+
>
5+
> # 抢先体验 Beta 版
6+
>
7+
> 您正在使用我们的最新功能预览版。为了在正式版中给您带来更完美的体验,我们需要您的帮助与反馈。
8+
>
9+
> **⚠️ 风险提示**:此版本尚在打磨中,可能存在偶发异常或未知问题,**请务必避免用于关键业务或生产环境**。
10+
>
11+
> **📣 我们需要您**:如果发现任何 Bug,或有功能改进建议,欢迎随时前往 **GitHub Issues** 提交反馈。
12+
>
13+
> 感谢您成为我们的首批体验官!
14+
15+
16+
# v3.0.0 - Nonomi (十六夜野宫) Beta 2
17+
18+
## 🚀 主要更新
19+
20+
- 新增 **Sudo 模式**,验证通过后可在设定时长内跳过抽取、窗口等敏感操作的重复验证(默认 60 秒,可在安全设置中调整为 10 至 600 秒);设置页面有独立有效期,关闭设置窗口即失效,配置 TOTP 仍始终需要重新验证;托盘菜单新增「退出 Sudo 模式」,只清除全局免验证状态[#283](https://github.com/SECTL/SecRandom/pull/283)
21+
- 新增 **证明链与提交队列**,抽取证明按追加式哈希链保存,普通模式的证明会立即保存并在后台提交服务端重放验证,失败自动按退避重试、断网或重启后继续提交;事后修改、删除旧证明、删光重建以及提交期间证明被改动都能被发现(服务端会记住每台设备见过的链头),「抽取验证」页可查看链的缺口、断链、被改动、超出链头、未落盘与过期,以及队列的待提交、失败、冲突与告警计数
22+
- 新增 **时间戳与名单摘要**,每份证明都会申请 RFC 3161 第三方时间戳,并承诺抽取时所用名单的「记录 ID → 姓名/学号/组别」映射摘要,抽取之后再改名单、把中签记录指向别人同样会被发现;两者都只发送摘要,不发送名单或抽取内容
23+
- 新增 **使用统计上报**,统计点名、抽奖与启动次数,上报字段与每日/每周/每月/总计四个周期口径均与 v2 一致;在隐私设置中选择「停止」后不再上报
24+
- 新增 **云备份可选包含抽取证明**,默认关闭,因为本地证明被删除后云端才是最后一份证据
25+
26+
## 💡 功能优化
27+
28+
- 优化 **在线状态上报**,不再由客户端上报公网 IP 与粗略地区(也不再请求第三方 IP 查询服务),位置改由服务端按访问地址统计,客户端只上报设备标识与设备类型
29+
- 优化 **归档与插件包校验**,为更新下载、插件市场索引与插件包引入压缩/解压/条目/清单大小预算,边读边计数以阻止解压炸弹与超大响应,被拒绝的插件包会标记为 .rejected;云端备份的清单、分片与整个归档增加长度上限、SHA-256 一致性与溢出保护
30+
- 优化 **路径与文件名校验**,名单的创建、重命名、导入与切换会拒绝相对路径、Windows 保留设备名与非法字符,Android 文件访问统一按 canonical 路径判断可访问性与可变性,安全凭据与语音凭据目录不可删除、重命名或移动
31+
- 优化 **凭据与外部地址限制**,OmniTTS 密钥与提供商及其规范化接口地址绑定,更换接口地址需要重新填写,旧版只按提供商保存的密钥不再自动信任;头像下载仅允许固定的 SECTL HTTPS 地址,且不会携带登录令牌
32+
- 优化 **设置文件完整性校验**,在完整性确认弹窗挂起期间阻止执行敏感命令与 secrandom:// 协议调用
33+
34+
## 🐛 修复问题
35+
36+
- 修复 **主界面导航**,在 Windows 与平板端从主界面进入设置(再返回)后,左侧栏高亮仍停留在「设置」而不回到原页面的问题;设置入口不再参与左侧栏选中[#268](https://github.com/SECTL/SecRandom/issues/268)
37+
- 修复 **抽取证明发布**,证明导出或上报失败会连带本次抽取失败的问题;现在抽取提交成功后再发布证明,发布失败只记录警告并保持待发布状态
38+
- 修复 **备份导入与插件安装**,异常的归档可能通过子路径覆盖 config/settings.json、config/device-uuid.json 等固定配置文件、插件替换失败会留下不完整插件目录的问题;现在两者失败时都会恢复原有文件
39+
40+
## 🙏 贡献者
41+
42+
- 感谢 [@CreeperAWA](https://github.com/CreeperAWA) 为本次更新贡献了 Sudo 模式
43+
44+
---
45+
46+
💝 **感谢所有贡献者为 SecRandom 项目付出的努力!**
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
using Avalonia;
2+
using Avalonia.Controls;
3+
using Avalonia.Headless;
4+
using Avalonia.Headless.XUnit;
5+
using Avalonia.Input;
6+
using Avalonia.Threading;
7+
using FluentAvalonia.UI.Controls;
8+
using SecRandom.Core.Attributes;
9+
using SecRandom.Core.Enums;
10+
using SecRandom.Views;
11+
12+
namespace SecRandom.Mobile.Tests;
13+
14+
/// <summary>
15+
/// Issue #268: the settings sidebar entry opens a separate window/view, so it must not take the
16+
/// navigation highlight away from the main page that stays visible.
17+
/// </summary>
18+
public sealed class MainViewSettingsNavigationTests
19+
{
20+
[AvaloniaFact]
21+
public void SettingsEntryKeepsItsPageMetadataWithoutSelectingItself()
22+
{
23+
var item = Assert.IsType<FANavigationViewItem>(MainView.CreateSettingsNavigationItem());
24+
25+
Assert.False(item.SelectsOnInvoked);
26+
var info = Assert.IsType<PageInfo>(item.Tag);
27+
Assert.Equal("settings", info.Id);
28+
Assert.Equal(PageLocation.Bottom, info.Location);
29+
}
30+
31+
[AvaloniaFact]
32+
public void InvokingTheSettingsEntryKeepsTheSelectedMainPage()
33+
{
34+
var rollCall = new FANavigationViewItem { Content = "Roll call" };
35+
var settings = MainView.CreateSettingsNavigationItem();
36+
var navigation = new FANavigationView
37+
{
38+
MenuItemsSource = new object[] { rollCall },
39+
FooterMenuItemsSource = new object[] { settings },
40+
SelectedItem = rollCall
41+
};
42+
43+
var window = new Window
44+
{
45+
Width = 900,
46+
Height = 600,
47+
Content = navigation
48+
};
49+
window.Show();
50+
Dispatcher.UIThread.RunJobs();
51+
52+
PageInfo? invoked = null;
53+
navigation.ItemInvoked += (_, args) =>
54+
{
55+
if (args.InvokedItemContainer is FANavigationViewItem { Tag: PageInfo info })
56+
invoked = info;
57+
};
58+
59+
var settingsControl = (Control)settings;
60+
var center = settingsControl.TranslatePoint(
61+
new Point(settingsControl.Bounds.Width / 2, settingsControl.Bounds.Height / 2), window);
62+
Assert.NotNull(center);
63+
64+
window.MouseDown(center.Value, MouseButton.Left);
65+
window.MouseUp(center.Value, MouseButton.Left);
66+
Dispatcher.UIThread.RunJobs();
67+
68+
Assert.NotNull(invoked);
69+
Assert.Equal("settings", invoked.Id);
70+
Assert.Same(rollCall, navigation.SelectedItem);
71+
72+
window.Close();
73+
}
74+
}

‎SecRandom/AGENTS.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ SecRandom/
163163
- Mobile route ViewModels belong in `ViewModels/Mobile/`; register them in the mobile branch of `BuildHost()` and keep page code-behind limited to visual-host responsibilities such as dialogs and animations.
164164
- Use `IAppHost.GetService<T>()` for existing service resolution patterns in views and services.
165165
- Views usually set `DataContext = this` and expose a `ViewModel` property.
166-
- Main default page: `main.rollCall`; settings default page: `settings.overview`, which separates aggregate roll-call list statistics from aggregate lottery-pool statistics.
166+
- Main default page: `main.rollCall`; settings default page: `settings.overview`, which separates aggregate roll-call list statistics from aggregate lottery-pool statistics. The main sidebar's settings entry opens the desktop settings window or the independent mobile `SettingsView` rather than a page in `MainView`'s own frame, so it is built by `MainView.CreateSettingsNavigationItem()` with `FANavigationViewItem.SelectsOnInvoked = false`: invoking it must not move the pane highlight off the main page that stays visible. The phone shell already restores its bottom tab the same way in `MobileRootView.SynchronizeBottomNavigation`.
167167
- The roll-call main page is bottom-pinned in the main window sidebar (`PageLocation.Bottom`), full-width, and title-hidden. Keep its page chrome controlled by `MoreSettings`.
168168
- Lottery main page ID is `main.lottery`; quick draw is not registered as a main navigation page, but its settings page remains `settings.picking.quickDraw`.
169169
- More settings includes roll-call and lottery page management options for the control panel side and per-control visibility; wire built-in draw pages through `MainConfigModel.MoreSettings` instead of duplicating local UI flags.

‎SecRandom/Views/MainView.axaml.cs‎

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -184,11 +184,7 @@ private void BuildNavigationMenuItems()
184184
return x;
185185
}));
186186

187-
var settingsPageInfo = new PageInfo(@"settings", FluentIcons.SettingsFilled, null, PageLocation.Bottom)
188-
{
189-
Name = Langs.Common.Resources.Feat_Settings
190-
};
191-
var settingsItem = settingsPageInfo.ToNavigationViewItemBase();
187+
var settingsItem = CreateSettingsNavigationItem();
192188
if (applySampleNav)
193189
{
194190
settingsItem.Classes.Add(@"SampleAppNav");
@@ -207,6 +203,24 @@ private void BuildNavigationMenuItems()
207203
}
208204
}
209205

206+
/// <summary>
207+
/// 构建设置入口。该入口打开独立设置窗口(桌面)或独立移动端设置视图,而不是本框架内的页面,
208+
/// 因此它不能参与导航选中,否则左侧高亮会离开仍然可见的当前页面。
209+
/// </summary>
210+
public static FANavigationViewItemBase CreateSettingsNavigationItem()
211+
{
212+
var settingsPageInfo = new PageInfo(@"settings", FluentIcons.SettingsFilled, null, PageLocation.Bottom)
213+
{
214+
Name = Langs.Common.Resources.Feat_Settings
215+
};
216+
217+
var settingsItem = settingsPageInfo.ToNavigationViewItemBase();
218+
if (settingsItem is FANavigationViewItem navigationItem)
219+
navigationItem.SelectsOnInvoked = false;
220+
221+
return settingsItem;
222+
}
223+
210224
public void SelectNavigationItemById(string id)
211225
{
212226
var info = PagesRegistryService.MainItems.FirstOrDefault(info => info.Id == id);

0 commit comments

Comments
 (0)