diff --git a/README.md b/README.md index b3892d4..315c3f6 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ PortRelay 把这件事变成三步:

通道卡片与实时流量 -
通道卡片:访问地址、授权状态、实际连接时长,以及近 10 分钟流量(动图为加速演示) +
通道卡片:访问地址、待处理的访问与延长申请、实际连接时长,以及近 10 分钟流量(动图为加速演示)

@@ -104,7 +104,7 @@ PortRelay 把这件事变成三步: -授权快到期或刚结束时,同事再打开同一个链接,就能看到自己的授权状态并申请延长。你选好时长批准后,页面会显示新的到期时间。 +**申请延长**:授权快到期或刚结束时,同事再打开同一个链接,就能看到自己的授权状态并申请延长,不用你重新发邀请。 @@ -114,18 +114,18 @@ PortRelay 把这件事变成三步:
+你这边,通道卡片上会出现「申请延长」。打开「连接与授权」,延长申请排在最前面,点「批准延长」选好时长即可,同事的页面随即显示新的到期时间。这里还列出待审批的访问申请、已授权设备(在线状态与剩余时间)、最近结束的授权和仍然有效的邀请链接,刚结束的设备也可以直接延长。 + +

+ 连接与授权:延长申请、待审批、已授权设备、最近结束 +

+ **通道组**:组卡片汇总整组的状态与流量,下面逐行列出每个访问地址,可以单独开关、单独邀请或移出组。

通道组

-**连接与授权管理**:访问申请和延长申请、已授权设备(在线状态与剩余时间)、最近结束的授权,以及仍然有效的邀请链接,都在这里处理。给设备延长只需选一个时长,不用重新发链接。 - -

- 授权管理 -

-
网页面板与更多截图
diff --git a/README_EN.md b/README_EN.md index 6569560..97cc21a 100644 --- a/README_EN.md +++ b/README_EN.md @@ -85,7 +85,7 @@ PortRelay turns that into three steps:

Tunnel card with live traffic -
A tunnel card: access address, grants, connected time and the last 10 minutes of traffic (sped up) +
A tunnel card: access address, pending access and extension requests, connected time and the last 10 minutes of traffic (sped up)

@@ -107,7 +107,7 @@ PortRelay turns that into three steps: -When access is about to run out or has just ended, the teammate opens the same link again, sees where their access stands and asks for more time. Once you pick a duration, the page shows the new expiry. +**Extensions.** When access is about to run out or has just ended, the teammate opens the same link again, sees where their access stands and asks for more time. You don't need to send a new invite. @@ -117,18 +117,18 @@ When access is about to run out or has just ended, the teammate opens the same l
+On your side, the tunnel card shows 申请延长 (Extension requested). Open 连接与授权 (Connections & access): extension requests are listed first, and you approve one by clicking 批准延长 (Approve) and picking a duration. The teammate's page then shows the new expiry. The same view lists pending access requests, authorized devices (with online state and remaining time), recently ended grants and active invite links, and a device whose access just ended can be extended from there too. + +

+ Access management: extension requests, pending requests, authorized devices, recently ended +

+ **Tunnel groups.** A group card sums up the state and traffic of the whole group and lists every access address. Each address can be switched, invited or moved out on its own.

Tunnel group

-**Access management.** Access and extension requests, authorized devices (with online state and remaining time), recently ended grants and active invite links all live here. Extending a device takes one pick of a duration, with no new link to send. - -

- Access management -

-
Web panel and more screenshots
diff --git a/docs/images/app-main-dark.png b/docs/images/app-main-dark.png index f9138a6..459f329 100644 Binary files a/docs/images/app-main-dark.png and b/docs/images/app-main-dark.png differ diff --git a/docs/images/app-main-light.png b/docs/images/app-main-light.png index d2c2e87..8ec1ecf 100644 Binary files a/docs/images/app-main-light.png and b/docs/images/app-main-light.png differ diff --git a/docs/images/live-traffic.gif b/docs/images/live-traffic.gif index 701cb5c..0e4f69f 100644 Binary files a/docs/images/live-traffic.gif and b/docs/images/live-traffic.gif differ diff --git a/docs/images/web-panel.png b/docs/images/web-panel.png index 10b08e5..950b7e2 100644 Binary files a/docs/images/web-panel.png and b/docs/images/web-panel.png differ diff --git a/tools/ui-snapshots/main.swift b/tools/ui-snapshots/main.swift index 20393cc..831e1ce 100644 --- a/tools/ui-snapshots/main.swift +++ b/tools/ui-snapshots/main.swift @@ -163,6 +163,12 @@ func makeDemoVM(tick: Int = 0) -> PortRelayViewModel { {"code":"gx2","note":"测试组","created_at":"\(t(-100))","expires_at":"\(t(86000))","max_uses":3,"lease_duration":"today","require_approval":true}]}}] """) vm.groups[0].rate_history = RateSeriesItem(end: ga.end, step: 5, up: zip(ga.up, gb.up).map { $0 + $1 }, down: zip(ga.down, gb.down).map { $0 + $1 }) + // 授权延长:赵六的访问 20 分钟前结束,刚申请延长;王五结束不到 2 小时,还可以延长 + vm.tunnels[0].renewals = decode([RenewalItem].self, """ + [{"tunnel_id":"t1","ip":"192.168.1.47","name":"赵六","invite":"ab1","ended_at":"\(t(-1200))","renew_before":"\(t(6000))", + "request":{"id":"rn1","tunnel_id":"t1","name":"赵六","ip":"192.168.1.47","invite":"ab1","at":"\(t(-60))","status":"pending","renewal":true}}, + {"tunnel_id":"t1","ip":"192.168.1.36","name":"王五","invite":"ab2","ended_at":"\(t(-3000))","renew_before":"\(t(4200))"}] + """) // 卡片与顶部统计的实时速率取曲线最后一个点 var totalIn: Int64 = 0, totalOut: Int64 = 0 for i in [0, 1, 3, 4] { @@ -283,15 +289,9 @@ MainActor.assumeIsolated { render("group-edit-member", CGSize(width: 520, height: 800), TunnelEditSheet(vm: demo, tunnel: demo.tunnels[3], isPresented: yes)) // 授权延长:申请延长、最近结束、已授权设备的「延长」 - let rvm = makeDemoVM() - rvm.tunnels[0].renewals = decode([RenewalItem].self, """ - [{"tunnel_id":"\(rvm.tunnels[0].id)","ip":"192.168.1.47","name":"赵六","invite":"ab1","ended_at":"\(t(-1200))","renew_before":"\(t(6000))", - "request":{"id":"rn1","tunnel_id":"\(rvm.tunnels[0].id)","name":"赵六","ip":"192.168.1.47","invite":"ab1","at":"\(t(-60))","status":"pending","renewal":true}}, - {"tunnel_id":"\(rvm.tunnels[0].id)","ip":"192.168.1.36","name":"王五","invite":"ab2","ended_at":"\(t(-3000))","renew_before":"\(t(4200))"}] - """) - render("readme-access", CGSize(width: 760, height: 800), ClientDetailsSheet(vm: rvm, tunnel: rvm.tunnels[0], initialTab: 2, isPresented: yes)) - render("renew-card", CGSize(width: 980, height: 220), TunnelCardView(tunnel: rvm.tunnels[0], vm: rvm, onEdit: {}, onViewClients: {}, onViewLeases: {}, onInvite: {}).padding(20)) - render("renew-invite-form", CGSize(width: 600, height: 400), InviteSheet(vm: rvm, tunnel: rvm.tunnels[0], autoGenerate: false, isPresented: yes)) + render("readme-access", CGSize(width: 760, height: 800), ClientDetailsSheet(vm: demo, tunnel: demo.tunnels[0], initialTab: 2, isPresented: yes)) + render("renew-card", CGSize(width: 980, height: 220), TunnelCardView(tunnel: demo.tunnels[0], vm: demo, onEdit: {}, onViewClients: {}, onViewLeases: {}, onInvite: {}).padding(20)) + render("renew-invite-form", CGSize(width: 600, height: 400), InviteSheet(vm: demo, tunnel: demo.tunnels[0], autoGenerate: false, isPresented: yes)) render("renew-custom", CGSize(width: 300, height: 90), CustomMinutesForm(actionTitle: "延长") { _ in }) // README 动图逐帧:卡片随时间推移(每帧 = 5 秒) diff --git a/tools/ui-snapshots/webpanel/main.go b/tools/ui-snapshots/webpanel/main.go index f447ead..81d6322 100644 --- a/tools/ui-snapshots/webpanel/main.go +++ b/tools/ui-snapshots/webpanel/main.go @@ -96,8 +96,11 @@ func main() { "metrics":{"tunnel_id":"t1","status":"online","active_conns":3,"total_conns":1287,"bytes_in":245039104,"bytes_out":8703180,"blocked_attempts":12, "dynamic_leases":[{"ip":"192.168.1.21","expires_at":%q,"name":"张三","invite":"a1","duration":"2h","started_at":%q}, {"ip":"192.168.1.35","expires_at":%q,"name":"王小明","invite":"a2","duration":"8h","awaiting_connect":true}], - "pending":[{"id":"r1","tunnel_id":"t1","name":"李四","ip":"192.168.1.88","invite":"a3","at":%q,"status":"pending"}]}}`, - at(-3725), at(10675), at(-3300), at(7000), at(-200), at(80000), at(-60)), + "pending":[{"id":"r1","tunnel_id":"t1","name":"李四","ip":"192.168.1.88","invite":"a3","at":%q,"status":"pending"}]}, + "renewals":[{"tunnel_id":"t1","ip":"192.168.1.47","name":"赵六","invite":"ab1","ended_at":%q,"renew_before":%q, + "request":{"id":"rn1","tunnel_id":"t1","name":"赵六","ip":"192.168.1.47","invite":"ab1","at":%q,"status":"pending","renewal":true}}, + {"tunnel_id":"t1","ip":"192.168.1.36","name":"王五","invite":"ab2","ended_at":%q,"renew_before":%q}]}`, + at(-3725), at(10675), at(-3300), at(7000), at(-200), at(80000), at(-60), at(-1200), at(6000), at(-60), at(-3000), at(4200)), member("g1a", "10.0.3.11", 33670, 1, -1500, 1320, 3203180, 18039104), member("g1b", "10.0.3.12", 33671, 2, -1400, 1100, 2203180, 12039104), member("g1c", "10.0.3.13", 33672, 0, 0, 0, 0, 0),