Skip to content

Commit fef8fab

Browse files
Vonngclaude
andcommitted
fix(console): polish speedtest, sidebar, and help chrome
- Speedtest: status+progress on their own row, wrapping control row whose button can no longer overflow; duration accepts seconds or minutes (converted to seconds for the backend); size default MiB to match the unit list; unique unit-menu DOM ids - InputUnitMenu renders the selected unit's label instead of its raw value, fixing untranslated/raw unit chips everywhere - Sidebar bucket rows: virtual row pitch now matches the 44px item, so selected and hovered highlights no longer overlap; empty states are translated - Help menu drops the previous language's feed immediately when switching to an uncached language; <html lang> applies before first paint; drive expansion "of"/Login leftovers translated Dictionary follow-ups ride along: chart-legend prefixes, speedtest duration units, storage tiering renames (存储分层/分层存储/性能测试), "LDAP 是否启用", table empty states, and the license page Trademark entry. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 5fc8f22 commit fef8fab

9 files changed

Lines changed: 107 additions & 54 deletions

File tree

web-app/src/StyleHandler.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
// You should have received a copy of the GNU Affero General Public License
1515
// along with this program. If not, see <http://www.gnu.org/licenses/>.
1616

17-
import React, { Fragment, useEffect } from "react";
17+
import React, { Fragment, useLayoutEffect } from "react";
1818
import { GlobalStyles, ThemeHandler } from "mds";
1919
import { ThemeProvider } from "styled-components";
2020
import merge from "lodash/merge";
@@ -41,8 +41,9 @@ const StyleHandler = ({ children }: IStyleHandler) => {
4141
const language = useSelector((state: AppState) => state.system.language);
4242

4343
// Keep <html lang> in sync for accessibility and the zh CSS overrides in
44-
// index.css; this wraps both the login page and the console.
45-
useEffect(() => {
44+
// index.css; this wraps both the login page and the console. Layout effect
45+
// so a persisted Chinese preference applies before first paint.
46+
useLayoutEffect(() => {
4647
document.documentElement.lang = language === "zh" ? "zh-CN" : "en";
4748
}, [language]);
4849

web-app/src/i18n/zh.ts

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,12 @@ export const zh: Record<string, string> = {
6767
Watch: "监视",
6868
Encryption: "加密",
6969
Events: "事件",
70-
Tiering: "分层",
70+
Tiering: "存储分层",
7171
"Site Replication": "站点复制",
7272
Configuration: "配置",
7373
Tools: "工具",
7474
Health: "健康报告",
75-
Performance: "吞吐测试",
75+
Performance: "性能测试",
7676
Profile: "性能剖析",
7777
Inspect: "对象检查",
7878
"Create Bucket": "创建存储桶",
@@ -109,7 +109,7 @@ export const zh: Record<string, string> = {
109109
"IAM Policies": "IAM 策略",
110110
"Key Management Service": "密钥管理服务",
111111
"Key Management Service Keys": "密钥管理服务密钥",
112-
Tiers: "存储层",
112+
Tiers: "分层存储",
113113
"OPENID Configurations": "OPENID 配置",
114114
"LDAP Configurations": "LDAP 配置",
115115

@@ -176,6 +176,28 @@ export const zh: Record<string, string> = {
176176
"Drives Free Inodes": "磁盘空闲 Inode",
177177
Upload: "上传",
178178

179+
// ---- Dashboard: chart legends (instance suffixes like "[server]" are
180+
// preserved by translateLegend; only these static prefixes translate) ------
181+
"Data Received": "数据接收",
182+
"Data Sent": "数据发送",
183+
"Internode Bytes Received": "节点间接收字节",
184+
"Internode Bytes Sent": "节点间发送字节",
185+
"CPU Usage Rate": "CPU 使用率",
186+
"Memory Used": "内存使用",
187+
"Open FDs": "打开文件描述符",
188+
"Read Syscalls": "读系统调用",
189+
"Write Syscalls": "写系统调用",
190+
"Node RChar": "节点读取字节",
191+
"Node WChar": "节点写入字节",
192+
"Free Inodes": "空闲 Inode",
193+
"Total Usable": "总可用",
194+
"Usable Free": "可用空闲",
195+
"Used Space": "已用空间",
196+
197+
// ---- Duration units (speedtest) -----------------------------------------
198+
s: "秒",
199+
min: "分钟",
200+
179201
// ---- Common buttons and dialogs -----------------------------------------
180202
Cancel: "取消",
181203
Confirm: "确认",

web-app/src/i18n/zhScreens.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -812,7 +812,7 @@ export const zhScreens: Record<string, string> = {
812812
"Last modified:": "最后修改:",
813813
"Last Modified:": "最后修改:",
814814
"Latency Histogram": "延迟直方图",
815-
"LDAP Enabled": "LDAP 已启用",
815+
"LDAP Enabled": "LDAP 是否启用",
816816
"Learn more about {link}.": "了解更多关于{link}的内容。",
817817
"Learn more about Access Keys": "了解访问密钥",
818818
"Learn more about ACCESS KEYS": "了解访问密钥",
@@ -1679,6 +1679,9 @@ export const zhScreens: Record<string, string> = {
16791679
"你需要额外的权限才能{name}。请联系 SILO 管理员为你授予 {scopes} 权限,以便{name}。",
16801680
"You require additional permissions in order to create a new Tier. Please ask your SILO administrator to grant you {permission} permission in order to create a Tier.":
16811681
"你需要额外权限才能创建新的存储层。请联系 SILO 管理员为你授予 {permission} 权限。",
1682+
"No buckets yet.": "暂无存储桶。",
1683+
"No buckets match this filter.": "没有符合筛选条件的存储桶。",
1684+
Trademark: "商标",
16821685
};
16831686

16841687
export default zhScreens;

web-app/src/screens/AnonymousAccess/AnonymousAccess.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { resetSystem } from "../../systemSlice";
2424
import LoadingComponent from "../../common/LoadingComponent";
2525
import ObjectManagerButton from "../Console/Common/ObjectManager/ObjectManagerButton";
2626
import { SiloBrand } from "../../common/SiloBrand";
27+
import { useT } from "i18n";
2728

2829
const ObjectBrowser = React.lazy(
2930
() => import("../Console/ObjectBrowser/ObjectBrowser"),
@@ -33,6 +34,7 @@ const ObjectManager = React.lazy(
3334
);
3435

3536
const AnonymousAccess = () => {
37+
const t = useT();
3638
const dispatch = useAppDispatch();
3739

3840
return (
@@ -63,7 +65,7 @@ const AnonymousAccess = () => {
6365
}}
6466
sx={{ color: "white", textTransform: "initial" }}
6567
>
66-
Login
68+
{t("Login")}
6769
</Button>
6870
<ObjectManagerButton />
6971
</div>

web-app/src/screens/Console/Common/FormComponents/InputUnitMenu/InputUnitMenu.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,11 @@ const InputUnitMenu = ({
6565
disabled={disabled}
6666
type={"button"}
6767
>
68-
{unitSelected}
68+
{unitsList.find((unit) => unit.value === unitSelected)?.label ??
69+
unitSelected}
6970
</UnitMenuButton>
7071
<DropdownSelector
71-
id={"upload-main-menu"}
72+
id={`${id}-menu`}
7273
options={unitsList}
7374
selectedOption={""}
7475
onSelect={(value) => handleClose(value)}

web-app/src/screens/Console/Dashboard/BasicDashboard/DriveInfoItem.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,9 @@ const DriveInfoItem = ({ drive }: ICardProps) => {
222222
<Box className="percentage-value">
223223
{availablePercentage.toFixed(2)}%
224224
</Box>
225-
<Box>of {niceBytes(totalSpace.toString())}</Box>
225+
<Box>
226+
{t("of")} {niceBytes(totalSpace.toString())}
227+
</Box>
226228
</Box>
227229
</Box>
228230
</Box>

web-app/src/screens/Console/HelpMenu.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,9 @@ const HelpMenu = () => {
208208
setHelpItemsBlog(cached);
209209
return;
210210
}
211+
// Nothing cached for this language yet: drop the other language's items
212+
// right away so a failed fetch can't leave them on screen.
213+
setHelpItemsBlog(SILO_BLOG_FALLBACK);
211214

212215
const abortController = new AbortController();
213216

web-app/src/screens/Console/Menu/Listing/BucketsListing.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,15 @@ import BucketListItem from "./BucketListItem";
2828
import VirtualizedList from "../../Common/VirtualizedList/VirtualizedList";
2929
import get from "lodash/get";
3030
import { useTheme } from "styled-components";
31+
import { useT } from "i18n";
3132
import BucketFiltering from "./BucketFiltering";
3233
import { useSelector } from "react-redux";
3334
import { useLocation } from "react-router-dom";
3435

3536
const ListBuckets = () => {
3637
const dispatch = useAppDispatch();
3738
const theme = useTheme();
39+
const t = useT();
3840
const { pathname = "" } = useLocation();
3941

4042
const filterBuckets = useSelector(
@@ -100,7 +102,7 @@ const ListBuckets = () => {
100102
color: get(theme, "menu.vertical.sectionLabelColor", "#8093AC"),
101103
}}
102104
>
103-
No buckets yet.
105+
{t("No buckets yet.")}
104106
</Box>
105107
)}
106108
{!loadingBuckets && records.length !== 0 && (
@@ -145,7 +147,7 @@ const ListBuckets = () => {
145147
<VirtualizedList
146148
rowRenderFunction={renderItemLine}
147149
totalItems={filteredRecords.length}
148-
defaultHeight={35}
150+
defaultHeight={44}
149151
/>
150152
</Box>
151153
)}
@@ -164,7 +166,7 @@ const ListBuckets = () => {
164166
),
165167
}}
166168
>
167-
No buckets match this filter.
169+
{t("No buckets match this filter.")}
168170
</Box>
169171
)}
170172
</Box>

web-app/src/screens/Console/Speedtest/Speedtest.tsx

Lines changed: 57 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,9 @@ const Speedtest = () => {
5454
null,
5555
);
5656
const [size, setSize] = useState<string>("64");
57-
const [sizeUnit, setSizeUnit] = useState<string>("MB");
57+
const [sizeUnit, setSizeUnit] = useState<string>("MiB");
5858
const [duration, setDuration] = useState<string>("10");
59+
const [durationUnit, setDurationUnit] = useState<string>("s");
5960
const [topDate, setTopDate] = useState<number>(0);
6061
const [currentValue, setCurrentValue] = useState<number>(0);
6162
const [totalSeconds, setTotalSeconds] = useState<number>(0);
@@ -73,8 +74,11 @@ const Speedtest = () => {
7374
const baseUrl = baseLocation.pathname;
7475

7576
const wsProt = wsProtocol(url.protocol);
77+
// Minutes are a display convenience; the backend always takes seconds.
78+
const durationSeconds =
79+
durationUnit === "m" ? String(Number(duration) * 60) : duration;
7680
const socket = new WebSocket(
77-
`${wsProt}://${url.hostname}:${port}${baseUrl}ws/speedtest?&size=${size}${sizeUnit}&duration=${duration}s`,
81+
`${wsProt}://${url.hostname}:${port}${baseUrl}ws/speedtest?&size=${size}${sizeUnit}&duration=${durationSeconds}s`,
7882
);
7983

8084
const baseDate = DateTime.now();
@@ -132,7 +136,7 @@ const Speedtest = () => {
132136
// reset start status
133137
setStart(false);
134138
}
135-
}, [size, sizeUnit, start, duration]);
139+
}, [size, sizeUnit, start, duration, durationUnit]);
136140

137141
useEffect(() => {
138142
const actualSeconds = (topDate - currentValue) / 1000;
@@ -177,6 +181,47 @@ const Speedtest = () => {
177181
resource={CONSOLE_UI_RESOURCE}
178182
>
179183
<Box withBorders>
184+
<Box
185+
sx={{
186+
display: "flex",
187+
alignItems: "center",
188+
gap: 16,
189+
marginBottom: 18,
190+
}}
191+
>
192+
<Box
193+
sx={{
194+
fontSize: 13,
195+
whiteSpace: "nowrap",
196+
display: "flex",
197+
alignItems: "center",
198+
gap: 8,
199+
}}
200+
>
201+
{start ? (
202+
<Fragment>
203+
{t("Speedtest in progress...")}
204+
<Loader style={{ width: 15, height: 15 }} />
205+
</Fragment>
206+
) : (
207+
<Fragment>
208+
{currStatus && !start ? (
209+
<b>{t("Speed Test results:")}</b>
210+
) : (
211+
<b>{t("Performance test")}</b>
212+
)}
213+
</Fragment>
214+
)}
215+
</Box>
216+
<Box sx={{ flex: 1, minWidth: 120 }}>
217+
<ProgressBarWrapper
218+
value={speedometerValue}
219+
ready={currStatus !== null && !start}
220+
indeterminate={start}
221+
size={"small"}
222+
/>
223+
</Box>
224+
</Box>
180225
<Box
181226
sx={{
182227
display: "flex",
@@ -186,38 +231,7 @@ const Speedtest = () => {
186231
rowGap: 14,
187232
}}
188233
>
189-
<Box sx={{ flex: "1 1 200px", minWidth: 180, maxWidth: 300 }}>
190-
<Box
191-
sx={{
192-
fontSize: 13,
193-
marginBottom: 8,
194-
}}
195-
>
196-
{start ? (
197-
<Fragment>
198-
{t("Speedtest in progress...")}
199-
<Loader style={{ width: 15, height: 15 }} />
200-
</Fragment>
201-
) : (
202-
<Fragment>
203-
{currStatus && !start ? (
204-
<b>{t("Speed Test results:")}</b>
205-
) : (
206-
<b>{t("Performance test")}</b>
207-
)}
208-
</Fragment>
209-
)}
210-
</Box>
211-
<Box>
212-
<ProgressBarWrapper
213-
value={speedometerValue}
214-
ready={currStatus !== null && !start}
215-
indeterminate={start}
216-
size={"small"}
217-
/>
218-
</Box>
219-
</Box>
220-
<Box sx={{ flex: "1 1 240px", minWidth: 220, maxWidth: 320 }}>
234+
<Box sx={{ flex: "1 1 260px", minWidth: 240, maxWidth: 360 }}>
221235
<InputBox
222236
id={"size"}
223237
name={"size"}
@@ -243,7 +257,7 @@ const Speedtest = () => {
243257
}
244258
/>
245259
</Box>
246-
<Box sx={{ flex: "1 1 240px", minWidth: 220, maxWidth: 320 }}>
260+
<Box sx={{ flex: "1 1 260px", minWidth: 240, maxWidth: 360 }}>
247261
<InputBox
248262
id={"duration"}
249263
name={"duration"}
@@ -258,10 +272,13 @@ const Speedtest = () => {
258272
disabled={start}
259273
overlayObject={
260274
<InputUnitMenu
261-
id={"size-unit"}
262-
onUnitChange={() => {}}
263-
unitSelected={"s"}
264-
unitsList={[{ label: "s", value: "s" }]}
275+
id={"duration-unit"}
276+
onUnitChange={setDurationUnit}
277+
unitSelected={durationUnit}
278+
unitsList={[
279+
{ label: t("s"), value: "s" },
280+
{ label: t("min"), value: "m" },
281+
]}
265282
disabled={start}
266283
/>
267284
}

0 commit comments

Comments
 (0)