fix: use fixed web duration choices
This commit is contained in:
@@ -8,7 +8,7 @@ NetStable 是一个独立的 Go Web 测速项目,用于测试“打开网页
|
||||
- 其他用户发起请求时返回“有其他用户正在测速,请稍等”。
|
||||
- 启动时可设置程序理论最高带宽,例如 `-bandwidth-limit-mbps 30` 将测试流量限制到 30 Mbps。
|
||||
- 页面展示当前设置的理论最高限值。
|
||||
- 一次测试按阶段执行:先连续下载指定时长,再连续上传同样时长。例如页面填 `30`,就是下载 30 秒、上传 30 秒。
|
||||
- 一次测试按阶段执行:先连续下载指定时长,再连续上传同样时长。页面固定提供 `15s` 和 `30s` 两档,默认 `30s`。
|
||||
- 保存每次测试的完整样本曲线、摘要、脱敏后的用户 IP、地区和运营商信息;ISP 缺失时显示“未知运营商”。
|
||||
- 所有历史测试用户数据通过网页和 `/api/records` 提供。
|
||||
- 历史时序图展示不同时段的平均下载速度变化。
|
||||
|
||||
@@ -32,6 +32,20 @@ func TestStaticAssetsIncludeMobileRecordLayout(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestStaticAssetsUseFixedDurationChoices(t *testing.T) {
|
||||
html, err := readAsset("index.html")
|
||||
if err != nil {
|
||||
t.Fatalf("read index.html: %v", err)
|
||||
}
|
||||
if !contains(html, `name="durationSeconds" type="radio" value="15"`) ||
|
||||
!contains(html, `name="durationSeconds" type="radio" value="30"`) {
|
||||
t.Fatalf("index.html missing fixed 15s/30s duration choices")
|
||||
}
|
||||
if contains(html, `id="durationSeconds" name="durationSeconds" type="number"`) {
|
||||
t.Fatalf("index.html should not expose free-form duration number input")
|
||||
}
|
||||
}
|
||||
|
||||
func contains(text, pattern string) bool {
|
||||
return strings.Contains(text, pattern)
|
||||
}
|
||||
|
||||
+6
-1
@@ -510,7 +510,12 @@ function value(id) {
|
||||
}
|
||||
|
||||
function numberValue(id) {
|
||||
return Number(document.getElementById(id).value);
|
||||
const field = document.getElementById(id);
|
||||
if (field) {
|
||||
return Number(field.value);
|
||||
}
|
||||
const selected = document.querySelector(`input[name="${id}"]:checked`);
|
||||
return selected ? Number(selected.value) : 0;
|
||||
}
|
||||
|
||||
function average(values) {
|
||||
|
||||
+12
-3
@@ -28,10 +28,19 @@
|
||||
<input id="httpUrl" name="httpUrl" placeholder="可选,例如 https://example.com/">
|
||||
</label>
|
||||
<div class="grid-2">
|
||||
<label>
|
||||
<fieldset class="choice-field">
|
||||
<span>每阶段时长(秒)</span>
|
||||
<input id="durationSeconds" name="durationSeconds" type="number" min="1" max="600" value="30">
|
||||
</label>
|
||||
<div class="segmented-control" role="radiogroup" aria-label="每阶段时长">
|
||||
<label>
|
||||
<input name="durationSeconds" type="radio" value="15">
|
||||
<span>15s</span>
|
||||
</label>
|
||||
<label>
|
||||
<input name="durationSeconds" type="radio" value="30" checked>
|
||||
<span>30s</span>
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
<label>
|
||||
<span>超时(毫秒)</span>
|
||||
<input id="timeoutMillis" name="timeoutMillis" type="number" min="100" max="30000" value="3000">
|
||||
|
||||
@@ -112,6 +112,12 @@ label {
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
min-height: 40px;
|
||||
@@ -128,6 +134,60 @@ input:focus {
|
||||
border-color: var(--blue);
|
||||
}
|
||||
|
||||
.choice-field {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.choice-field > span {
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.segmented-control {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 4px;
|
||||
min-height: 40px;
|
||||
padding: 4px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
background: #fbfdff;
|
||||
}
|
||||
|
||||
.segmented-control label {
|
||||
position: relative;
|
||||
display: block;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.segmented-control input {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.segmented-control span {
|
||||
display: grid;
|
||||
min-height: 32px;
|
||||
place-items: center;
|
||||
border-radius: 6px;
|
||||
color: var(--muted);
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.segmented-control input:checked + span {
|
||||
color: #ffffff;
|
||||
background: var(--blue);
|
||||
}
|
||||
|
||||
.segmented-control input:focus-visible + span {
|
||||
outline: 2px solid rgba(37, 99, 235, 0.3);
|
||||
outline-offset: 1px;
|
||||
}
|
||||
|
||||
.grid-2 {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
@@ -332,6 +392,14 @@ tr:last-child td {
|
||||
min-height: 44px;
|
||||
}
|
||||
|
||||
.segmented-control {
|
||||
min-height: 44px;
|
||||
}
|
||||
|
||||
.segmented-control span {
|
||||
min-height: 36px;
|
||||
}
|
||||
|
||||
.metrics {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 8px;
|
||||
|
||||
Reference in New Issue
Block a user