@xulongzhe/clawbench-desktop-linux-x64-payload 0.105.0 → 0.106.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/resources/app.asar +0 -0
- package/resources/login.html +354 -137
- package/resources/url-utils.js +58 -0
package/package.json
CHANGED
package/resources/app.asar
CHANGED
|
Binary file
|
package/resources/login.html
CHANGED
|
@@ -13,9 +13,10 @@
|
|
|
13
13
|
password_label: 'Password',
|
|
14
14
|
password_placeholder: 'Password',
|
|
15
15
|
connect: 'Connect',
|
|
16
|
-
protocol: 'Protocol',
|
|
17
16
|
server_address: 'Server Address',
|
|
18
|
-
|
|
17
|
+
add: 'Add',
|
|
18
|
+
save: 'Save',
|
|
19
|
+
edit_server: 'Edit Server',
|
|
19
20
|
cancel: 'Cancel',
|
|
20
21
|
add_server: 'Add Server',
|
|
21
22
|
confirm: 'Confirm',
|
|
@@ -24,8 +25,13 @@
|
|
|
24
25
|
delete_server_msg: 'Delete this server?',
|
|
25
26
|
saved_servers: 'Saved Servers',
|
|
26
27
|
authenticating: 'Authenticating...',
|
|
28
|
+
saving: 'Saving...',
|
|
29
|
+
name_label: 'Name (optional)',
|
|
30
|
+
name_placeholder: 'e.g. Home NAS',
|
|
31
|
+
duplicate_name: 'That name is already used',
|
|
27
32
|
enter_password: 'Please enter password',
|
|
28
|
-
|
|
33
|
+
enter_full_address: 'Enter a full address with http:// or https:// and a port, e.g. https://192.168.1.100:20000',
|
|
34
|
+
duplicate_server: 'That address is already saved',
|
|
29
35
|
connection_failed: 'Connection failed: ',
|
|
30
36
|
connect_error: 'Unable to connect. Check address and network.',
|
|
31
37
|
wrong_password: 'Wrong password',
|
|
@@ -37,7 +43,16 @@
|
|
|
37
43
|
splash_rendering: 'Rendering…',
|
|
38
44
|
splash_initializing: 'Initializing app…',
|
|
39
45
|
splash_cancel: 'Cancel connection',
|
|
40
|
-
window_close: 'Close'
|
|
46
|
+
window_close: 'Close',
|
|
47
|
+
// Version-mismatch gate. The desktop and server versions differ in either
|
|
48
|
+
// direction, so the wording must not assume the app is the older side —
|
|
49
|
+
// the two `_older`/`_newer` variants cover that.
|
|
50
|
+
version_mismatch_title: 'Version mismatch',
|
|
51
|
+
version_mismatch_body: 'Desktop version: %1\nServer version: %2\n\nThe two versions may be incompatible: some features may behave unexpectedly or stop working.',
|
|
52
|
+
version_mismatch_older: 'The desktop app is older than the server. Updating is recommended.',
|
|
53
|
+
version_mismatch_newer: 'The desktop app is newer than the server. Downloading will align it to the server version (a downgrade).',
|
|
54
|
+
version_mismatch_download: 'Download',
|
|
55
|
+
version_mismatch_continue: 'Continue anyway'
|
|
41
56
|
},
|
|
42
57
|
zh: {
|
|
43
58
|
slogan: '从终端到桌面',
|
|
@@ -45,9 +60,10 @@
|
|
|
45
60
|
password_label: '登录密码',
|
|
46
61
|
password_placeholder: '密码',
|
|
47
62
|
connect: '连 接',
|
|
48
|
-
protocol: '协议',
|
|
49
63
|
server_address: '服务器地址',
|
|
50
|
-
|
|
64
|
+
add: '添 加',
|
|
65
|
+
save: '保 存',
|
|
66
|
+
edit_server: '编辑服务器',
|
|
51
67
|
cancel: '取 消',
|
|
52
68
|
add_server: '添加服务器',
|
|
53
69
|
confirm: '确认',
|
|
@@ -56,8 +72,13 @@
|
|
|
56
72
|
delete_server_msg: '删除此服务器?',
|
|
57
73
|
saved_servers: '已保存服务器',
|
|
58
74
|
authenticating: '认证中...',
|
|
75
|
+
saving: '保存中...',
|
|
76
|
+
name_label: '名称(选填)',
|
|
77
|
+
name_placeholder: '例如 家里的 NAS',
|
|
78
|
+
duplicate_name: '该名称已被使用',
|
|
59
79
|
enter_password: '请输入密码',
|
|
60
|
-
|
|
80
|
+
enter_full_address: '请输入完整地址,包含 http:// 或 https:// 与端口,例如 https://192.168.1.100:20000',
|
|
81
|
+
duplicate_server: '该地址已存在',
|
|
61
82
|
connection_failed: '连接失败: ',
|
|
62
83
|
connect_error: '无法连接到服务器,请检查地址和网络。',
|
|
63
84
|
wrong_password: '密码错误',
|
|
@@ -69,7 +90,15 @@
|
|
|
69
90
|
splash_rendering: '正在渲染…',
|
|
70
91
|
splash_initializing: '正在初始化应用…',
|
|
71
92
|
splash_cancel: '取消连接',
|
|
72
|
-
window_close: '关闭'
|
|
93
|
+
window_close: '关闭',
|
|
94
|
+
// Version-mismatch gate. See the en block for why the wording is split by
|
|
95
|
+
// direction rather than assuming the app is older.
|
|
96
|
+
version_mismatch_title: '版本不一致',
|
|
97
|
+
version_mismatch_body: '桌面端版本: %1\n服务器版本: %2\n\n两者可能不兼容:部分功能可能异常或无法使用。',
|
|
98
|
+
version_mismatch_older: '桌面端版本低于服务器,建议更新。',
|
|
99
|
+
version_mismatch_newer: '桌面端版本高于服务器,下载将对齐到服务器版本(降级)。',
|
|
100
|
+
version_mismatch_download: '下载',
|
|
101
|
+
version_mismatch_continue: '仍然继续'
|
|
73
102
|
}
|
|
74
103
|
};
|
|
75
104
|
var LANG = 'en';
|
|
@@ -994,6 +1023,16 @@
|
|
|
994
1023
|
font-weight: 500;
|
|
995
1024
|
}
|
|
996
1025
|
|
|
1026
|
+
/* Row actions: edit + delete, kept in one group so the info block can
|
|
1027
|
+
stretch without pushing the icons apart. */
|
|
1028
|
+
.server-item-actions {
|
|
1029
|
+
flex-shrink: 0;
|
|
1030
|
+
display: flex;
|
|
1031
|
+
align-items: center;
|
|
1032
|
+
gap: 2px;
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
.server-edit-btn,
|
|
997
1036
|
.server-delete-btn {
|
|
998
1037
|
flex-shrink: 0;
|
|
999
1038
|
border: none;
|
|
@@ -1008,15 +1047,26 @@
|
|
|
1008
1047
|
transition: background 0.1s, color 0.1s;
|
|
1009
1048
|
}
|
|
1010
1049
|
|
|
1050
|
+
.server-edit-btn:hover {
|
|
1051
|
+
background: rgba(var(--accent-rgb), 0.12);
|
|
1052
|
+
color: var(--accent-color);
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1011
1055
|
.server-delete-btn:hover {
|
|
1012
1056
|
background: rgba(220, 38, 38, 0.1);
|
|
1013
1057
|
color: var(--color-red);
|
|
1014
1058
|
}
|
|
1015
1059
|
|
|
1060
|
+
.server-item.active .server-edit-btn,
|
|
1016
1061
|
.server-item.active .server-delete-btn {
|
|
1017
1062
|
color: var(--text-muted);
|
|
1018
1063
|
}
|
|
1019
1064
|
|
|
1065
|
+
.server-item.active .server-edit-btn:hover {
|
|
1066
|
+
color: var(--accent-color);
|
|
1067
|
+
background: rgba(var(--accent-rgb), 0.15);
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1020
1070
|
.server-item.active .server-delete-btn:hover {
|
|
1021
1071
|
color: var(--color-red);
|
|
1022
1072
|
background: rgba(220, 38, 38, 0.12);
|
|
@@ -1037,69 +1087,12 @@
|
|
|
1037
1087
|
margin-bottom: 6px;
|
|
1038
1088
|
}
|
|
1039
1089
|
|
|
1040
|
-
/*
|
|
1041
|
-
.
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
margin-bottom: 16px;
|
|
1045
|
-
}
|
|
1046
|
-
|
|
1047
|
-
.protocol-group label {
|
|
1048
|
-
display: flex;
|
|
1049
|
-
align-items: center;
|
|
1050
|
-
gap: 6px;
|
|
1051
|
-
font-size: 14px;
|
|
1090
|
+
/* Title shown above the add form when it is reused for editing */
|
|
1091
|
+
.form-title {
|
|
1092
|
+
font-size: 15px;
|
|
1093
|
+
font-weight: 600;
|
|
1052
1094
|
color: var(--text-primary);
|
|
1053
|
-
|
|
1054
|
-
}
|
|
1055
|
-
|
|
1056
|
-
.protocol-group input[type="radio"] {
|
|
1057
|
-
appearance: none;
|
|
1058
|
-
-webkit-appearance: none;
|
|
1059
|
-
width: 18px;
|
|
1060
|
-
height: 18px;
|
|
1061
|
-
border: 2px solid var(--border-color);
|
|
1062
|
-
border-radius: 50%;
|
|
1063
|
-
position: relative;
|
|
1064
|
-
cursor: pointer;
|
|
1065
|
-
transition: border-color 0.2s;
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
|
-
.protocol-group input[type="radio"]:checked {
|
|
1069
|
-
border-color: var(--accent-color);
|
|
1070
|
-
}
|
|
1071
|
-
|
|
1072
|
-
.protocol-group input[type="radio"]:checked::after {
|
|
1073
|
-
content: '';
|
|
1074
|
-
position: absolute;
|
|
1075
|
-
top: 3px;
|
|
1076
|
-
left: 3px;
|
|
1077
|
-
width: 8px;
|
|
1078
|
-
height: 8px;
|
|
1079
|
-
border-radius: 50%;
|
|
1080
|
-
background: var(--accent-color);
|
|
1081
|
-
}
|
|
1082
|
-
|
|
1083
|
-
/* Host + Port row */
|
|
1084
|
-
.host-row {
|
|
1085
|
-
display: flex;
|
|
1086
|
-
align-items: center;
|
|
1087
|
-
gap: 4px;
|
|
1088
|
-
}
|
|
1089
|
-
|
|
1090
|
-
.host-row input:first-child {
|
|
1091
|
-
flex: 1;
|
|
1092
|
-
}
|
|
1093
|
-
|
|
1094
|
-
.host-row .colon {
|
|
1095
|
-
color: var(--text-muted);
|
|
1096
|
-
font-size: 18px;
|
|
1097
|
-
line-height: 44px;
|
|
1098
|
-
}
|
|
1099
|
-
|
|
1100
|
-
.host-row input:last-child {
|
|
1101
|
-
width: 90px;
|
|
1102
|
-
flex-shrink: 0;
|
|
1095
|
+
margin-bottom: 14px;
|
|
1103
1096
|
}
|
|
1104
1097
|
|
|
1105
1098
|
/* Input fields — matches LoginView.vue input style */
|
|
@@ -1458,6 +1451,94 @@
|
|
|
1458
1451
|
background: var(--bg-tertiary);
|
|
1459
1452
|
color: var(--text-primary);
|
|
1460
1453
|
}
|
|
1454
|
+
|
|
1455
|
+
/* ── Version-mismatch gate ──────────────────────────────────────────────
|
|
1456
|
+
Replaces the loading splash when the desktop/server versions differ. Like
|
|
1457
|
+
the splash it is hidden unless a body class is set (added by
|
|
1458
|
+
__versionMismatchShow), so the same document can serve the login form, the
|
|
1459
|
+
splash and the gate. Styled as a card, mirroring the app's DialogOverlay so
|
|
1460
|
+
the prompt reads as the same UI as the in-app dialogs. */
|
|
1461
|
+
.gate { display: none; }
|
|
1462
|
+
|
|
1463
|
+
body.gate-mode .gate {
|
|
1464
|
+
position: fixed;
|
|
1465
|
+
inset: 0;
|
|
1466
|
+
/* Above the splash: the gate replaces it, but both may be display:flex at
|
|
1467
|
+
once (the page is reused and the main process does not always clear
|
|
1468
|
+
splash-mode), so the gate must win the paint order. */
|
|
1469
|
+
z-index: 200;
|
|
1470
|
+
display: flex;
|
|
1471
|
+
align-items: center;
|
|
1472
|
+
justify-content: center;
|
|
1473
|
+
padding: 0 20px;
|
|
1474
|
+
background: var(--bg-primary);
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
body.gate-mode .content { display: none; }
|
|
1478
|
+
|
|
1479
|
+
.gate-card {
|
|
1480
|
+
width: 100%;
|
|
1481
|
+
max-width: 360px;
|
|
1482
|
+
padding: 18px 20px 14px;
|
|
1483
|
+
background: var(--bg-secondary);
|
|
1484
|
+
border-radius: 14px;
|
|
1485
|
+
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.16), 0 14px 44px rgba(0, 0, 0, 0.3);
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
.gate-title {
|
|
1489
|
+
font-size: 15px;
|
|
1490
|
+
font-weight: 600;
|
|
1491
|
+
color: var(--text-primary);
|
|
1492
|
+
margin-bottom: 8px;
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
.gate-body {
|
|
1496
|
+
font-size: 13px;
|
|
1497
|
+
line-height: 1.6;
|
|
1498
|
+
color: var(--text-secondary);
|
|
1499
|
+
white-space: pre-line;
|
|
1500
|
+
word-break: break-word;
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
.gate-direction {
|
|
1504
|
+
margin-top: 10px;
|
|
1505
|
+
font-size: 12px;
|
|
1506
|
+
line-height: 1.5;
|
|
1507
|
+
color: var(--text-muted);
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
.gate-actions {
|
|
1511
|
+
display: flex;
|
|
1512
|
+
justify-content: flex-end;
|
|
1513
|
+
gap: 8px;
|
|
1514
|
+
margin-top: 16px;
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
.gate-btn {
|
|
1518
|
+
padding: 7px 14px;
|
|
1519
|
+
font-size: 13px;
|
|
1520
|
+
font-family: inherit;
|
|
1521
|
+
border-radius: 6px;
|
|
1522
|
+
cursor: pointer;
|
|
1523
|
+
transition: opacity 150ms ease, background 150ms ease, color 150ms ease;
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
.gate-continue {
|
|
1527
|
+
color: var(--text-secondary);
|
|
1528
|
+
background: var(--bg-tertiary);
|
|
1529
|
+
border: 1px solid var(--border-color);
|
|
1530
|
+
}
|
|
1531
|
+
.gate-continue:hover {
|
|
1532
|
+
color: var(--text-primary);
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
.gate-download {
|
|
1536
|
+
color: #fff;
|
|
1537
|
+
background: var(--accent-color);
|
|
1538
|
+
border: none;
|
|
1539
|
+
font-weight: 600;
|
|
1540
|
+
}
|
|
1541
|
+
.gate-download:hover { opacity: 0.9; }
|
|
1461
1542
|
</style>
|
|
1462
1543
|
</head>
|
|
1463
1544
|
<body>
|
|
@@ -1474,6 +1555,22 @@
|
|
|
1474
1555
|
<button type="button" class="splash-cancel" id="splashCancel" data-i18n="splash_cancel">取消连接</button>
|
|
1475
1556
|
</div>
|
|
1476
1557
|
|
|
1558
|
+
<!-- Version-mismatch gate. Shown by the main process (see splash.ts /
|
|
1559
|
+
versionGate.ts) INSTEAD of the loading splash when the desktop and server
|
|
1560
|
+
versions differ in either direction. Blocking: it has no cancel button, so
|
|
1561
|
+
the only ways out are "download" and "continue". -->
|
|
1562
|
+
<div class="gate" id="versionGate">
|
|
1563
|
+
<div class="gate-card">
|
|
1564
|
+
<div class="gate-title" id="gateTitle" data-i18n="version_mismatch_title">版本不一致</div>
|
|
1565
|
+
<div class="gate-body" id="gateBody"></div>
|
|
1566
|
+
<div class="gate-direction" id="gateDirection"></div>
|
|
1567
|
+
<div class="gate-actions">
|
|
1568
|
+
<button type="button" class="gate-btn gate-continue" id="gateContinue" data-i18n="version_mismatch_continue">仍然继续</button>
|
|
1569
|
+
<button type="button" class="gate-btn gate-download" id="gateDownload" data-i18n="version_mismatch_download">下载</button>
|
|
1570
|
+
</div>
|
|
1571
|
+
</div>
|
|
1572
|
+
</div>
|
|
1573
|
+
|
|
1477
1574
|
<div class="bg-gradient"></div>
|
|
1478
1575
|
<div class="bg-grid"></div>
|
|
1479
1576
|
|
|
@@ -1538,31 +1635,20 @@
|
|
|
1538
1635
|
</div>
|
|
1539
1636
|
</form>
|
|
1540
1637
|
|
|
1541
|
-
<!-- Add server form (hidden by default) -->
|
|
1638
|
+
<!-- Add / edit server form (hidden by default) -->
|
|
1542
1639
|
<form id="addServerForm" onsubmit="return false;" style="display:none;">
|
|
1543
|
-
|
|
1640
|
+
<div class="form-title" id="addFormTitle" style="display:none;"></div>
|
|
1641
|
+
|
|
1642
|
+
<!-- Optional display name -->
|
|
1544
1643
|
<div class="field-group">
|
|
1545
|
-
<span class="field-label" data-i18n="
|
|
1546
|
-
<
|
|
1547
|
-
<label>
|
|
1548
|
-
<input type="radio" name="addProtocol" value="https" checked>
|
|
1549
|
-
HTTPS
|
|
1550
|
-
</label>
|
|
1551
|
-
<label>
|
|
1552
|
-
<input type="radio" name="addProtocol" value="http">
|
|
1553
|
-
HTTP
|
|
1554
|
-
</label>
|
|
1555
|
-
</div>
|
|
1644
|
+
<span class="field-label" data-i18n="name_label">名称(选填)</span>
|
|
1645
|
+
<input type="text" id="addName" data-i18n-placeholder="name_placeholder" placeholder="例如 家里的 NAS" maxlength="40" autocorrect="off" autocapitalize="sentences" spellcheck="false">
|
|
1556
1646
|
</div>
|
|
1557
1647
|
|
|
1558
|
-
<!--
|
|
1648
|
+
<!-- Server address (protocol + host + port in one field) -->
|
|
1559
1649
|
<div class="field-group">
|
|
1560
1650
|
<span class="field-label" data-i18n="server_address">服务器地址</span>
|
|
1561
|
-
<
|
|
1562
|
-
<input type="text" id="addHost" placeholder="192.168.1.100" autocorrect="off" autocapitalize="none" spellcheck="false">
|
|
1563
|
-
<span class="colon">:</span>
|
|
1564
|
-
<input type="number" id="addPort" value="20000" inputmode="numeric">
|
|
1565
|
-
</div>
|
|
1651
|
+
<input type="text" id="addAddress" placeholder="https://192.168.1.100:20000" autocorrect="off" autocapitalize="none" spellcheck="false" inputmode="url">
|
|
1566
1652
|
</div>
|
|
1567
1653
|
|
|
1568
1654
|
<!-- Password -->
|
|
@@ -1578,7 +1664,7 @@
|
|
|
1578
1664
|
</div>
|
|
1579
1665
|
|
|
1580
1666
|
<button type="submit" class="connect-btn" id="addConnectBtn">
|
|
1581
|
-
<span id="addBtnText" data-i18n="
|
|
1667
|
+
<span id="addBtnText" data-i18n="add">添 加</span>
|
|
1582
1668
|
</button>
|
|
1583
1669
|
<button type="button" class="cancel-btn" id="cancelAddBtn" data-i18n="cancel">取 消</button>
|
|
1584
1670
|
|
|
@@ -1622,6 +1708,7 @@
|
|
|
1622
1708
|
// SVG icons
|
|
1623
1709
|
var SVG_SERVER = '<svg class="server-item-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="2" width="20" height="8" rx="2"/><rect x="2" y="14" width="20" height="8" rx="2"/><circle cx="6" cy="6" r="1"/><circle cx="6" cy="18" r="1"/></svg>';
|
|
1624
1710
|
var SVG_X = '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>';
|
|
1711
|
+
var SVG_PENCIL = '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 20h9"/><path d="M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4Z"/></svg>';
|
|
1625
1712
|
|
|
1626
1713
|
function applyI18n() {
|
|
1627
1714
|
document.querySelectorAll('[data-i18n]').forEach(function(el) {
|
|
@@ -1679,6 +1766,37 @@
|
|
|
1679
1766
|
if (el) el.classList.remove('is-fading');
|
|
1680
1767
|
};
|
|
1681
1768
|
|
|
1769
|
+
// ── Version-mismatch gate ───────────────────────────────────────────────
|
|
1770
|
+
// Shown INSTEAD of the loading splash when the desktop and server versions
|
|
1771
|
+
// differ in either direction (see desktop/src/main/versionGate.ts). The main
|
|
1772
|
+
// process calls __versionMismatchShow with the two versions and which side is
|
|
1773
|
+
// newer; the buttons report the user's choice back through the native bridge.
|
|
1774
|
+
//
|
|
1775
|
+
// The direction decides the wording: "newer" means the desktop app is AHEAD,
|
|
1776
|
+
// where downloading aligns DOWN to the server — so "update" would be wrong.
|
|
1777
|
+
/** Fill the gate with the version details. Called from the main process. */
|
|
1778
|
+
window.__versionMismatchShow = function(info) {
|
|
1779
|
+
if (!info) return;
|
|
1780
|
+
var body = document.getElementById('gateBody');
|
|
1781
|
+
if (body) {
|
|
1782
|
+
body.textContent = t('version_mismatch_body')
|
|
1783
|
+
.replace('%1', info.clientVersion || '')
|
|
1784
|
+
.replace('%2', info.serverVersion || '');
|
|
1785
|
+
}
|
|
1786
|
+
var dir = document.getElementById('gateDirection');
|
|
1787
|
+
if (dir) {
|
|
1788
|
+
dir.textContent = t(info.direction === 'newer'
|
|
1789
|
+
? 'version_mismatch_newer'
|
|
1790
|
+
: 'version_mismatch_older');
|
|
1791
|
+
}
|
|
1792
|
+
document.body.classList.add('gate-mode');
|
|
1793
|
+
};
|
|
1794
|
+
|
|
1795
|
+
/** Clear the gate state. The page is REUSED, so this runs before every show. */
|
|
1796
|
+
window.__versionMismatchReset = function() {
|
|
1797
|
+
document.body.classList.remove('gate-mode');
|
|
1798
|
+
};
|
|
1799
|
+
|
|
1682
1800
|
if (SPLASH_MODE) {
|
|
1683
1801
|
document.body.classList.add('splash-mode');
|
|
1684
1802
|
}
|
|
@@ -1715,6 +1833,20 @@
|
|
|
1715
1833
|
try { ClawBenchNative.cancelSplash(); } catch(e) {}
|
|
1716
1834
|
});
|
|
1717
1835
|
}
|
|
1836
|
+
// Version-gate buttons. Wired here (not in the show hook) so they work
|
|
1837
|
+
// even if the gate is shown after the page has already loaded.
|
|
1838
|
+
var continueBtn = document.getElementById('gateContinue');
|
|
1839
|
+
if (continueBtn) {
|
|
1840
|
+
continueBtn.addEventListener('click', function() {
|
|
1841
|
+
try { ClawBenchNative.versionContinue(); } catch(e) {}
|
|
1842
|
+
});
|
|
1843
|
+
}
|
|
1844
|
+
var downloadBtn = document.getElementById('gateDownload');
|
|
1845
|
+
if (downloadBtn) {
|
|
1846
|
+
downloadBtn.addEventListener('click', function() {
|
|
1847
|
+
try { ClawBenchNative.versionDownload(); } catch(e) {}
|
|
1848
|
+
});
|
|
1849
|
+
}
|
|
1718
1850
|
return;
|
|
1719
1851
|
}
|
|
1720
1852
|
|
|
@@ -1768,64 +1900,81 @@
|
|
|
1768
1900
|
}
|
|
1769
1901
|
});
|
|
1770
1902
|
|
|
1771
|
-
// Event: add and
|
|
1903
|
+
// Event: save the add/edit form. Both "Add Server" and the row pencil land
|
|
1904
|
+
// here; the form's editUrl data attribute is what distinguishes them.
|
|
1905
|
+
// Saving never connects — the user picks the server and presses Connect.
|
|
1772
1906
|
document.getElementById('addServerForm').addEventListener('submit', function(e) {
|
|
1773
1907
|
e.preventDefault();
|
|
1774
1908
|
if (connecting) return;
|
|
1775
1909
|
hideError('addErrorMsg');
|
|
1776
1910
|
|
|
1777
|
-
var
|
|
1778
|
-
|
|
1911
|
+
var url = buildServerUrl(document.getElementById('addAddress').value);
|
|
1912
|
+
if (!url) {
|
|
1913
|
+
showError('addErrorMsg', t('enter_full_address'));
|
|
1914
|
+
document.getElementById('addAddress').focus();
|
|
1915
|
+
return;
|
|
1916
|
+
}
|
|
1779
1917
|
var password = document.getElementById('addPassword').value;
|
|
1780
|
-
var
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1918
|
+
var name = normalizeServerName(document.getElementById('addName').value);
|
|
1919
|
+
// The URL this form is editing ('' when adding). Captured before save so
|
|
1920
|
+
// the rename branch can retire the old entry afterwards.
|
|
1921
|
+
var editingUrl = document.getElementById('addServerForm').getAttribute('data-edit-url') || '';
|
|
1922
|
+
|
|
1923
|
+
// Renaming onto another saved server would silently overwrite that
|
|
1924
|
+
// server's password (saveServer keys on URL). Refuse instead.
|
|
1925
|
+
var clash = savedServers.some(function(s) {
|
|
1926
|
+
return s.url === url && s.url !== editingUrl;
|
|
1927
|
+
});
|
|
1928
|
+
if (clash) {
|
|
1929
|
+
showError('addErrorMsg', t('duplicate_server'));
|
|
1785
1930
|
return;
|
|
1786
1931
|
}
|
|
1787
|
-
|
|
1788
|
-
|
|
1932
|
+
// Names are unique labels. The edited entry is exempt from matching
|
|
1933
|
+
// itself; unnamed servers never collide with each other.
|
|
1934
|
+
if (findServerNameConflict(savedServers, name, editingUrl)) {
|
|
1935
|
+
showError('addErrorMsg', t('duplicate_name'));
|
|
1936
|
+
return;
|
|
1789
1937
|
}
|
|
1790
1938
|
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1939
|
+
setSaving(true);
|
|
1940
|
+
// Both calls go through ipcRenderer.invoke, so failures surface as
|
|
1941
|
+
// rejections. Chain them: the rename must delete the old entry only after
|
|
1942
|
+
// the new one is written, and the list must reload after both settle.
|
|
1795
1943
|
try {
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1944
|
+
Promise.resolve(ClawBenchNative.saveServerNamed(url, password, name))
|
|
1945
|
+
.then(function() {
|
|
1946
|
+
// Only when the address actually changed: deleting the same URL
|
|
1947
|
+
// would drop the entry we just saved.
|
|
1948
|
+
if (editingUrl && editingUrl !== url) {
|
|
1949
|
+
return Promise.resolve(ClawBenchNative.removeServer(editingUrl));
|
|
1950
|
+
}
|
|
1951
|
+
})
|
|
1952
|
+
.then(function() {
|
|
1953
|
+
setSaving(false);
|
|
1954
|
+
hideAddForm();
|
|
1955
|
+
})
|
|
1956
|
+
.catch(function(err) {
|
|
1957
|
+
showError('addErrorMsg', t('connection_failed') + (err && err.message ? err.message : ''));
|
|
1958
|
+
setSaving(false);
|
|
1959
|
+
});
|
|
1803
1960
|
} catch(e) {
|
|
1804
1961
|
showError('addErrorMsg', t('connection_failed') + e.message);
|
|
1805
|
-
|
|
1962
|
+
setSaving(false);
|
|
1806
1963
|
}
|
|
1807
1964
|
});
|
|
1808
1965
|
|
|
1809
|
-
// Event: blur on the
|
|
1810
|
-
// Blur, not paste/input: the field is only rewritten after the user leaves
|
|
1811
|
-
// so typing is never disturbed mid-keystroke. Reading .value (rather
|
|
1812
|
-
// clipboard data) also sidesteps WebView clipboard restrictions.
|
|
1813
|
-
document.getElementById('
|
|
1814
|
-
var
|
|
1815
|
-
//
|
|
1816
|
-
|
|
1966
|
+
// Event: blur on the address field -> normalize a full address in place.
|
|
1967
|
+
// Blur, not paste/input: the field is only rewritten after the user leaves
|
|
1968
|
+
// it, so typing is never disturbed mid-keystroke. Reading .value (rather
|
|
1969
|
+
// than clipboard data) also sidesteps WebView clipboard restrictions.
|
|
1970
|
+
document.getElementById('addAddress').addEventListener('blur', function() {
|
|
1971
|
+
var url = buildServerUrl(document.getElementById('addAddress').value);
|
|
1972
|
+
// Incomplete address -> leave the field exactly as the user typed it; the
|
|
1973
|
+
// submit handler reports what is missing.
|
|
1974
|
+
if (!url) return;
|
|
1817
1975
|
|
|
1818
1976
|
hideError('addErrorMsg');
|
|
1819
|
-
document.getElementById('
|
|
1820
|
-
// Only override the port when the URL carried one explicitly; the field
|
|
1821
|
-
// keeps its default (20000) otherwise.
|
|
1822
|
-
if (parsed.port !== null) {
|
|
1823
|
-
document.getElementById('addPort').value = parsed.port;
|
|
1824
|
-
}
|
|
1825
|
-
// No scheme in the input -> leave the radio untouched.
|
|
1826
|
-
if (parsed.protocol !== null) {
|
|
1827
|
-
document.querySelector('input[name="addProtocol"][value="' + parsed.protocol + '"]').checked = true;
|
|
1828
|
-
}
|
|
1977
|
+
document.getElementById('addAddress').value = url;
|
|
1829
1978
|
});
|
|
1830
1979
|
|
|
1831
1980
|
// Event: show add form
|
|
@@ -1881,11 +2030,19 @@
|
|
|
1881
2030
|
html += ' <div class="server-item-info">';
|
|
1882
2031
|
html += ' <span class="server-indicator"></span>';
|
|
1883
2032
|
html += ' ' + SVG_SERVER;
|
|
1884
|
-
|
|
2033
|
+
// A named server shows its name; an unnamed one falls back to the
|
|
2034
|
+
// address. The full address always stays in the title tooltip, so a
|
|
2035
|
+
// named row is still identifiable (and copying it is possible).
|
|
2036
|
+
html += ' <span class="server-item-url" title="' + escHtml(srv.url) + '">' + escHtml(normalizeServerName(srv.name) || formatHost(srv.url)) + '</span>';
|
|
2037
|
+
html += ' </div>';
|
|
2038
|
+
html += ' <div class="server-item-actions">';
|
|
2039
|
+
html += ' <button class="server-edit-btn" onclick="event.stopPropagation(); editServer(\'' + escJs(srv.url) + '\')" title="' + t('edit_server') + '">';
|
|
2040
|
+
html += ' ' + SVG_PENCIL;
|
|
2041
|
+
html += ' </button>';
|
|
2042
|
+
html += ' <button class="server-delete-btn" onclick="event.stopPropagation(); deleteServer(\'' + escJs(srv.url) + '\')" title="' + t('delete') + '">';
|
|
2043
|
+
html += ' ' + SVG_X;
|
|
2044
|
+
html += ' </button>';
|
|
1885
2045
|
html += ' </div>';
|
|
1886
|
-
html += ' <button class="server-delete-btn" onclick="event.stopPropagation(); deleteServer(\'' + escJs(srv.url) + '\')" title="' + t('delete') + '">';
|
|
1887
|
-
html += ' ' + SVG_X;
|
|
1888
|
-
html += ' </button>';
|
|
1889
2046
|
html += '</div>';
|
|
1890
2047
|
});
|
|
1891
2048
|
|
|
@@ -1999,17 +2156,59 @@
|
|
|
1999
2156
|
});
|
|
2000
2157
|
}
|
|
2001
2158
|
|
|
2159
|
+
/**
|
|
2160
|
+
* Show the form for adding a NEW server: empty fields, "Add" button.
|
|
2161
|
+
*/
|
|
2002
2162
|
function showAddForm() {
|
|
2163
|
+
var form = document.getElementById('addServerForm');
|
|
2164
|
+
// Empty data-edit-url is what marks this as an add, not an edit.
|
|
2165
|
+
form.setAttribute('data-edit-url', '');
|
|
2166
|
+
document.getElementById('addFormTitle').style.display = 'none';
|
|
2167
|
+
document.getElementById('addName').value = '';
|
|
2168
|
+
document.getElementById('addAddress').value = '';
|
|
2169
|
+
document.getElementById('addPassword').value = '';
|
|
2170
|
+
document.getElementById('addBtnText').textContent = t('add');
|
|
2171
|
+
|
|
2172
|
+
document.getElementById('serverListSection').style.display = 'none';
|
|
2173
|
+
document.getElementById('connectForm').style.display = 'none';
|
|
2174
|
+
document.getElementById('addServerBtn').style.display = 'none';
|
|
2175
|
+
form.style.display = 'block';
|
|
2176
|
+
}
|
|
2177
|
+
|
|
2178
|
+
/**
|
|
2179
|
+
* Reuse the add form to edit an existing server. data-edit-url holds the
|
|
2180
|
+
* entry being edited so submit can tell the two modes apart and retire the
|
|
2181
|
+
* old URL when the address changes.
|
|
2182
|
+
*/
|
|
2183
|
+
function editServer(url) {
|
|
2184
|
+
var srv = savedServers.find(function(s) { return s.url === url; });
|
|
2185
|
+
if (!srv) return;
|
|
2186
|
+
|
|
2187
|
+
var form = document.getElementById('addServerForm');
|
|
2188
|
+
form.setAttribute('data-edit-url', url);
|
|
2189
|
+
var title = document.getElementById('addFormTitle');
|
|
2190
|
+
title.textContent = t('edit_server');
|
|
2191
|
+
title.style.display = 'block';
|
|
2192
|
+
document.getElementById('addName').value = normalizeServerName(srv.name);
|
|
2193
|
+
document.getElementById('addAddress').value = srv.url;
|
|
2194
|
+
// Prefill the saved password so saving without retyping keeps it.
|
|
2195
|
+
document.getElementById('addPassword').value = srv.password || '';
|
|
2196
|
+
document.getElementById('addBtnText').textContent = t('save');
|
|
2197
|
+
|
|
2003
2198
|
document.getElementById('serverListSection').style.display = 'none';
|
|
2004
2199
|
document.getElementById('connectForm').style.display = 'none';
|
|
2005
2200
|
document.getElementById('addServerBtn').style.display = 'none';
|
|
2006
|
-
|
|
2201
|
+
form.style.display = 'block';
|
|
2202
|
+
hideError('addErrorMsg');
|
|
2203
|
+
document.getElementById('addAddress').focus();
|
|
2007
2204
|
}
|
|
2008
2205
|
|
|
2009
2206
|
function hideAddForm() {
|
|
2010
2207
|
document.getElementById('addServerForm').style.display = 'none';
|
|
2011
2208
|
document.getElementById('addErrorMsg').classList.remove('visible');
|
|
2012
|
-
|
|
2209
|
+
// Reload from native rather than re-rendering the stale in-memory array:
|
|
2210
|
+
// saving changed the list (new entry, promoted head, renamed URL).
|
|
2211
|
+
loadServerList();
|
|
2013
2212
|
}
|
|
2014
2213
|
|
|
2015
2214
|
function formatHost(url) {
|
|
@@ -2050,13 +2249,31 @@
|
|
|
2050
2249
|
} else {
|
|
2051
2250
|
btn.disabled = false;
|
|
2052
2251
|
if (isAddForm) {
|
|
2053
|
-
|
|
2252
|
+
// The form doubles as add and edit, so restore whichever label its
|
|
2253
|
+
// current mode calls for rather than always "Add".
|
|
2254
|
+
var editing = document.getElementById('addServerForm').getAttribute('data-edit-url');
|
|
2255
|
+
btn.innerHTML = '<span id="addBtnText">' + t(editing ? 'save' : 'add') + '</span>';
|
|
2054
2256
|
} else {
|
|
2055
2257
|
btn.innerHTML = '<span id="btnText">' + t('connect') + '</span>';
|
|
2056
2258
|
}
|
|
2057
2259
|
}
|
|
2058
2260
|
}
|
|
2059
2261
|
|
|
2262
|
+
/**
|
|
2263
|
+
* Busy state for the add/edit form's own Save/Add button. Separate from
|
|
2264
|
+
* setConnecting because saving does not authenticate: showing "Authenticating"
|
|
2265
|
+
* would misdescribe what is happening.
|
|
2266
|
+
*/
|
|
2267
|
+
function setSaving(saving) {
|
|
2268
|
+
var btn = document.getElementById('addConnectBtn');
|
|
2269
|
+
if (saving) {
|
|
2270
|
+
btn.disabled = true;
|
|
2271
|
+
btn.innerHTML = '<div class="spinner"></div><span>' + t('saving') + '</span>';
|
|
2272
|
+
} else {
|
|
2273
|
+
setConnecting(false, true);
|
|
2274
|
+
}
|
|
2275
|
+
}
|
|
2276
|
+
|
|
2060
2277
|
// Called by native code on connection failure.
|
|
2061
2278
|
// msg is the Chromium load-error description (e.g. "ERR_CONNECTION_REFUSED")
|
|
2062
2279
|
// delivered from the main process's did-fail-load handler; empty when the
|
package/resources/url-utils.js
CHANGED
|
@@ -46,3 +46,61 @@ function parseServerInput(text) {
|
|
|
46
46
|
|
|
47
47
|
return { protocol: protocol, host: host, port: port };
|
|
48
48
|
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Build the canonical server URL from a single free-text address.
|
|
52
|
+
*
|
|
53
|
+
* Strict on purpose: the login form no longer has a protocol radio or a port
|
|
54
|
+
* field, so the address must carry BOTH explicitly. A bare host or a
|
|
55
|
+
* scheme-less `host:port` returns null and the caller shows "enter a full
|
|
56
|
+
* address" rather than guessing a scheme/port the user never chose.
|
|
57
|
+
*
|
|
58
|
+
* Normalisation the parser already gives us: scheme lowercased, path/query/
|
|
59
|
+
* fragment dropped, surrounding whitespace trimmed.
|
|
60
|
+
*
|
|
61
|
+
* Returns 'scheme://host:port', or null when the input is not a complete
|
|
62
|
+
* server address. Callers must treat null as "reject, do not save".
|
|
63
|
+
*/
|
|
64
|
+
function buildServerUrl(text) {
|
|
65
|
+
var parsed = parseServerInput(text);
|
|
66
|
+
if (!parsed || !parsed.protocol || !parsed.port) return null;
|
|
67
|
+
return parsed.protocol + '://' + parsed.host + ':' + parsed.port;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Normalise a server's display name: trimmed. Empty means "no name", which the
|
|
72
|
+
* list renders as the address instead.
|
|
73
|
+
*
|
|
74
|
+
* Returns '' for anything that is not a string, so callers can pass the raw
|
|
75
|
+
* field value without guarding.
|
|
76
|
+
*/
|
|
77
|
+
function normalizeServerName(text) {
|
|
78
|
+
if (typeof text !== 'string') return '';
|
|
79
|
+
return text.trim();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Find the server whose NAME equals `name`, ignoring `exceptUrl`.
|
|
84
|
+
*
|
|
85
|
+
* Names are unique labels, so the form refuses to save a duplicate. The
|
|
86
|
+
* comparison is case-insensitive (users do not think of "Home" and "home" as
|
|
87
|
+
* different servers) and an empty `name` never matches — it means "no name",
|
|
88
|
+
* and unnamed servers must not collide with each other.
|
|
89
|
+
*
|
|
90
|
+
* `exceptUrl` is the entry currently being edited: a server must not collide
|
|
91
|
+
* with itself when its name is left unchanged.
|
|
92
|
+
*
|
|
93
|
+
* Returns the conflicting entry (an object with at least `.url`), or null.
|
|
94
|
+
*/
|
|
95
|
+
function findServerNameConflict(servers, name, exceptUrl) {
|
|
96
|
+
var wanted = normalizeServerName(name).toLowerCase();
|
|
97
|
+
if (!wanted) return null;
|
|
98
|
+
if (!Array.isArray(servers)) return null;
|
|
99
|
+
for (var i = 0; i < servers.length; i++) {
|
|
100
|
+
var srv = servers[i];
|
|
101
|
+
if (!srv) continue;
|
|
102
|
+
if (exceptUrl && srv.url === exceptUrl) continue;
|
|
103
|
+
if (normalizeServerName(srv.name).toLowerCase() === wanted) return srv;
|
|
104
|
+
}
|
|
105
|
+
return null;
|
|
106
|
+
}
|