docs: README 跟上授权延长与通道组,截图和动图改为一条命令重新生成
README - 申请延长写清规则:授权有效期间也可申请,有效的从原到期时间顺延、已结束的从批准时起算, 被撤销的不能延长;批准延长时通道会开到授权结束(与最长开启时长的关系) - 修正通道组「一起开关」与「可以单独开关」的矛盾:闲置按整组计算,每个地址仍可单独开关 - 时长选项用界面上的「通道开启期间」;快速开始按实际流程写(保存后自动弹出邀请设置) - 新增同事端申请延长的三张截图;流程图补上 30 分钟、自定义与 2 小时内可申请延长 截图与动图 - 授权管理、编辑通道、邀请文案、授权成功页、网页面板(含通道组)更新为当前界面 - 离屏渲染时控件按激活状态绘制,开关不再显示成灰色 - 新增 make readme-images:App 离屏渲染、动图合成、授权页与网页面板的无头 Chrome 截图一次完成, 写入 docs/images;授权页 HTML 由服务端测试用模拟状态生成,网页面板用本机模拟 API 界面 - 计时文字第二个单位补零(27分05秒),卡片上的徽标不再随秒数左右跳动;App 与网页面板一致 - 待审批与延长申请的时间格式统一为不带秒;App 预览的分享文案补上延长提示
This commit is contained in:
1 parent
6f2a1472b6
commit
3272e66247
26 files changed
+392
-37
No files matched your search
@@ -0,0 +1,85 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/DeepFal/PortRelay/core/acl"
|
||||
"github.com/DeepFal/PortRelay/core/protocol"
|
||||
"github.com/DeepFal/PortRelay/core/traffic"
|
||||
)
|
||||
|
||||
// TestReadmePages 用模拟状态生成 README 截图用的授权页 HTML(tools/ui-snapshots/web.sh 调用),平时跳过。
|
||||
// 通道只放在内存里,不监听端口;访问者 IP 直接写在请求上。
|
||||
func TestReadmePages(t *testing.T) {
|
||||
out := os.Getenv("PORTRELAY_README_PAGES")
|
||||
if out == "" {
|
||||
t.Skip("PORTRELAY_README_PAGES not set")
|
||||
}
|
||||
m := NewManager(33000, 34000, testToken)
|
||||
m.PublicHost = "192.168.1.10"
|
||||
cfg := protocol.TunnelConfig{ID: "mysql", Name: "MySQL 数据库", Target: "10.0.1.16:3306", ListenPort: 33660, Enabled: true}
|
||||
m.mu.Lock()
|
||||
m.tunnels[cfg.ID] = &Tunnel{Config: cfg, whitelist: acl.NewWhitelist(nil), activeConns: map[string]*ActiveClient{},
|
||||
meter: traffic.NewMeter(), closed: make(chan struct{})}
|
||||
m.mu.Unlock()
|
||||
|
||||
do := func(method, path, ip string, form url.Values) *httptest.ResponseRecorder {
|
||||
var body io.Reader
|
||||
if form != nil {
|
||||
body = strings.NewReader(form.Encode())
|
||||
}
|
||||
r := httptest.NewRequest(method, "http://192.168.1.10:33000"+path, body)
|
||||
if form != nil {
|
||||
r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
}
|
||||
r.RemoteAddr = ip + ":52000"
|
||||
w := httptest.NewRecorder()
|
||||
m.handleHTTPRequest(w, r)
|
||||
return w
|
||||
}
|
||||
follow := func(w *httptest.ResponseRecorder, ip string) *httptest.ResponseRecorder {
|
||||
if loc := w.Header().Get("Location"); loc != "" {
|
||||
return do(http.MethodGet, loc, ip, nil)
|
||||
}
|
||||
return w
|
||||
}
|
||||
save := func(name string, w *httptest.ResponseRecorder) {
|
||||
t.Helper()
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("%s: %d %s", name, w.Code, w.Body)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(out, name+".html"), w.Body.Bytes(), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// ① 打开邀请链接 → ③ 授权成功
|
||||
inv := m.state.createInvite(cfg.ID, "张三", 24*time.Hour, 1, "2h", false)
|
||||
save("auth-form", do(http.MethodGet, "/i/"+inv.Code, "192.168.1.21", nil))
|
||||
save("auth-ok", do(http.MethodPost, "/auth", "192.168.1.21", url.Values{"code": {inv.Code}, "name": {"张三"}}))
|
||||
|
||||
// ② 需要审批
|
||||
ask := m.state.createInvite(cfg.ID, "测试组", 24*time.Hour, 0, "today", true)
|
||||
save("auth-pending", follow(do(http.MethodPost, "/auth", "192.168.1.88", url.Values{"code": {ask.Code}, "name": {"李四"}}), "192.168.1.88"))
|
||||
|
||||
// ④ ⑤ ⑥ 两小时前开始使用、20 分钟前到期,再打开链接申请延长,管理员批准 2 小时
|
||||
ip := "192.168.1.47"
|
||||
rn := m.state.createInvite(cfg.ID, "赵六", 24*time.Hour, 1, "2h", false)
|
||||
do(http.MethodPost, "/auth", ip, url.Values{"code": {rn.Code}, "name": {"赵六"}})
|
||||
m.state.startLease(cfg.ID, ip, nil, time.Now().Add(-140*time.Minute))
|
||||
save("auth-renew", do(http.MethodGet, "/i/"+rn.Code, ip, nil))
|
||||
status := do(http.MethodPost, "/renew", ip, url.Values{"code": {rn.Code}})
|
||||
save("auth-renew-pending", follow(status, ip))
|
||||
if resp := m.extendLease(protocol.ExtendLeaseMessage{TunnelID: cfg.ID, IP: ip, Minutes: 120}); !resp.Success {
|
||||
t.Fatal(resp.Error)
|
||||
}
|
||||
save("auth-renew-ok", follow(status, ip))
|
||||
}
|
||||
Reference in new issue
Block a user