mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-08-10 08:43:34 +08:00
✨ feat(connection): 优化数据源选择并支持全局搜索
- 新增全部分类、跨分类名称与类型键搜索及空结果提示 - 将卡片网格重构为响应式平面目录并完善键盘可达性 - 补齐六语言文案及语言切换、状态重置与过滤行为回归
This commit is contained in:
@@ -421,6 +421,36 @@ body[data-theme='light'] .redis-viewer-workbench .ant-radio-button-wrapper-check
|
||||
border-top: none !important;
|
||||
}
|
||||
|
||||
.gn-connection-type-row {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.gn-connection-type-row:hover {
|
||||
background: rgba(16, 24, 40, 0.05);
|
||||
}
|
||||
|
||||
.gn-connection-type-row:focus-visible {
|
||||
outline: 2px solid color-mix(in srgb, var(--gn-accent, #1677ff) 68%, transparent);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
body[data-theme='dark'] .gn-connection-type-row:hover {
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
|
||||
.gn-connection-type-group:hover:not([aria-pressed='true']) {
|
||||
background: rgba(16, 24, 40, 0.035) !important;
|
||||
}
|
||||
|
||||
.gn-connection-type-group:focus-visible {
|
||||
outline: 2px solid color-mix(in srgb, var(--gn-accent, #1677ff) 68%, transparent);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
body[data-theme='dark'] .gn-connection-type-group:hover:not([aria-pressed='true']) {
|
||||
background: rgba(255, 255, 255, 0.045) !important;
|
||||
}
|
||||
|
||||
/* Custom Title Bar Close Button Hover */
|
||||
body[data-platform='windows'] .titlebar-window-controls {
|
||||
gap: 8px;
|
||||
|
||||
@@ -4,6 +4,7 @@ import { act, create, type ReactTestRenderer } from "react-test-renderer";
|
||||
import { readFileSync } from "node:fs";
|
||||
|
||||
import { setCurrentLanguage } from "../i18n";
|
||||
import { getAllConnectionTypeCatalogItems } from "../utils/connectionTypeCatalog";
|
||||
import { getCustomConnectionDriverHelp } from "../utils/driverImportGuidance";
|
||||
|
||||
const storeState = {
|
||||
@@ -66,7 +67,38 @@ const findClickableByAnyText = (renderer: ReactTestRenderer, texts: string[]) =>
|
||||
)[0];
|
||||
|
||||
const findClickableCard = (renderer: ReactTestRenderer, text: string) =>
|
||||
renderer.root.findAll((node) => node.props?.role === "button" && textContent(node).includes(text))[0];
|
||||
renderer.root.findAll(
|
||||
(node) =>
|
||||
(node.props?.role === "button" ||
|
||||
typeof node.props?.["data-connection-type-key"] === "string") &&
|
||||
textContent(node).includes(text),
|
||||
)[0];
|
||||
|
||||
const findConnectionTypeButtons = (renderer: ReactTestRenderer) =>
|
||||
renderer.root.findAll(
|
||||
(node) =>
|
||||
node.type === "button" &&
|
||||
typeof node.props?.["data-connection-type-key"] === "string",
|
||||
);
|
||||
|
||||
const findConnectionTypeGroup = (
|
||||
renderer: ReactTestRenderer,
|
||||
groupKey: string,
|
||||
) =>
|
||||
renderer.root.findAll(
|
||||
(node) =>
|
||||
node.type === "button" &&
|
||||
node.props?.["data-connection-group-key"] === groupKey,
|
||||
)[0];
|
||||
|
||||
const findInputByPlaceholder = (
|
||||
renderer: ReactTestRenderer,
|
||||
placeholder: string,
|
||||
) =>
|
||||
renderer.root.findAll(
|
||||
(node) =>
|
||||
node.type === "input" && node.props?.placeholder === placeholder,
|
||||
)[0];
|
||||
|
||||
/**
|
||||
* 展开「连接 URI」分组。
|
||||
@@ -173,6 +205,7 @@ vi.mock("@ant-design/icons", () => {
|
||||
ThunderboltOutlined: Icon,
|
||||
DownOutlined: Icon,
|
||||
RightOutlined: Icon,
|
||||
SearchOutlined: Icon,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -385,6 +418,14 @@ describe("ConnectionModal i18n", () => {
|
||||
});
|
||||
|
||||
expect(textContent(renderer!.toJSON())).toContain("选择数据源类型");
|
||||
expect(
|
||||
textContent(
|
||||
findConnectionTypeGroup(
|
||||
renderer!,
|
||||
"connection_modal.step1.group.all",
|
||||
),
|
||||
),
|
||||
).toBe("全部");
|
||||
|
||||
await act(async () => {
|
||||
storeState.setLanguagePreference("en-US");
|
||||
@@ -392,9 +433,58 @@ describe("ConnectionModal i18n", () => {
|
||||
|
||||
expect(storeState.setLanguagePreference).toHaveBeenCalledWith("en-US");
|
||||
expect(textContent(renderer!.toJSON())).toContain("Select connection type");
|
||||
expect(
|
||||
textContent(
|
||||
findConnectionTypeGroup(
|
||||
renderer!,
|
||||
"connection_modal.step1.group.all",
|
||||
),
|
||||
),
|
||||
).toBe("All");
|
||||
expect(
|
||||
textContent(
|
||||
findConnectionTypeGroup(
|
||||
renderer!,
|
||||
"connection_modal.step1.group.other",
|
||||
),
|
||||
),
|
||||
).toBe("Other");
|
||||
expect(textContent(renderer!.toJSON())).not.toContain("选择数据源类型");
|
||||
});
|
||||
|
||||
it("retranslates data source groups when resolved system language changes", async () => {
|
||||
storeState.languagePreference = "system";
|
||||
setCurrentLanguage("zh-CN");
|
||||
const { default: ConnectionModal } = await import("./ConnectionModal");
|
||||
const onClose = vi.fn();
|
||||
|
||||
let renderer: ReactTestRenderer;
|
||||
await act(async () => {
|
||||
renderer = create(<ConnectionModal open onClose={onClose} />);
|
||||
});
|
||||
expect(
|
||||
textContent(
|
||||
findConnectionTypeGroup(
|
||||
renderer!,
|
||||
"connection_modal.step1.group.all",
|
||||
),
|
||||
),
|
||||
).toBe("全部");
|
||||
|
||||
setCurrentLanguage("en-US");
|
||||
await act(async () => {
|
||||
renderer!.update(<ConnectionModal open onClose={onClose} />);
|
||||
});
|
||||
expect(
|
||||
textContent(
|
||||
findConnectionTypeGroup(
|
||||
renderer!,
|
||||
"connection_modal.step1.group.all",
|
||||
),
|
||||
),
|
||||
).toBe("All");
|
||||
});
|
||||
|
||||
it.each(["legacy", "v2"] as const)(
|
||||
"renders localized create flow copy for %s ui",
|
||||
async (uiVersion) => {
|
||||
@@ -1239,6 +1329,116 @@ describe("ConnectionModal i18n", () => {
|
||||
expect(pageText).not.toContain("Custom (自定义)");
|
||||
});
|
||||
|
||||
it("searches across all data sources, keeps category state consistent, and resets on reopen", async () => {
|
||||
storeState.appearance.uiVersion = "legacy";
|
||||
setCurrentLanguage("en-US");
|
||||
const { default: ConnectionModal } = await import("./ConnectionModal");
|
||||
const onClose = vi.fn();
|
||||
|
||||
let renderer: ReactTestRenderer;
|
||||
await act(async () => {
|
||||
renderer = create(<ConnectionModal open onClose={onClose} />);
|
||||
});
|
||||
|
||||
const expectedKeys = new Set(
|
||||
getAllConnectionTypeCatalogItems().map((item) => item.key),
|
||||
);
|
||||
expect(
|
||||
findConnectionTypeButtons(renderer!).map(
|
||||
(node) => node.props["data-connection-type-key"],
|
||||
),
|
||||
).toEqual([...expectedKeys]);
|
||||
|
||||
await act(async () => {
|
||||
findConnectionTypeGroup(
|
||||
renderer!,
|
||||
"connection_modal.step1.group.nosql",
|
||||
).props.onClick();
|
||||
});
|
||||
expect(
|
||||
findConnectionTypeButtons(renderer!).map(
|
||||
(node) => node.props["data-connection-type-key"],
|
||||
),
|
||||
).toEqual(["mongodb", "redis", "elasticsearch"]);
|
||||
|
||||
await act(async () => {
|
||||
findInputByPlaceholder(
|
||||
renderer!,
|
||||
"Search data source name",
|
||||
).props.onChange({
|
||||
target: { value: " DIROS " },
|
||||
});
|
||||
});
|
||||
expect(
|
||||
findConnectionTypeGroup(
|
||||
renderer!,
|
||||
"connection_modal.step1.group.all",
|
||||
).props["aria-pressed"],
|
||||
).toBe(true);
|
||||
expect(
|
||||
findConnectionTypeButtons(renderer!).map(
|
||||
(node) => node.props["data-connection-type-key"],
|
||||
),
|
||||
).toEqual(["diros"]);
|
||||
expect(textContent(renderer!.toJSON())).toContain("Doris");
|
||||
|
||||
await act(async () => {
|
||||
findInputByPlaceholder(
|
||||
renderer!,
|
||||
"Search data source name",
|
||||
).props.onChange({
|
||||
target: { value: "not-a-real-data-source" },
|
||||
});
|
||||
});
|
||||
expect(findConnectionTypeButtons(renderer!)).toHaveLength(0);
|
||||
expect(textContent(renderer!.toJSON())).toContain(
|
||||
"No matching data source",
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
renderer!.update(<ConnectionModal open={false} onClose={onClose} />);
|
||||
});
|
||||
await act(async () => {
|
||||
renderer!.update(<ConnectionModal open onClose={onClose} />);
|
||||
});
|
||||
expect(
|
||||
findInputByPlaceholder(renderer!, "Search data source name").props.value,
|
||||
).toBe("");
|
||||
expect(findConnectionTypeButtons(renderer!)).toHaveLength(
|
||||
expectedKeys.size,
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
findConnectionTypeGroup(
|
||||
renderer!,
|
||||
"connection_modal.step1.group.other",
|
||||
).props.onClick();
|
||||
});
|
||||
expect(
|
||||
findConnectionTypeButtons(renderer!).map(
|
||||
(node) => node.props["data-connection-type-key"],
|
||||
),
|
||||
).toEqual(["jvm", "custom"]);
|
||||
});
|
||||
|
||||
it("uses native buttons for category and data source keyboard interaction", async () => {
|
||||
const { default: ConnectionModal } = await import("./ConnectionModal");
|
||||
|
||||
let renderer: ReactTestRenderer;
|
||||
await act(async () => {
|
||||
renderer = create(<ConnectionModal open onClose={vi.fn()} />);
|
||||
});
|
||||
|
||||
expect(
|
||||
findConnectionTypeGroup(
|
||||
renderer!,
|
||||
"connection_modal.step1.group.all",
|
||||
).props.type,
|
||||
).toBe("button");
|
||||
expect(findClickableCard(renderer!, "MySQL").type).toBe("button");
|
||||
expect(findClickableCard(renderer!, "MySQL").props.type).toBe("button");
|
||||
});
|
||||
|
||||
it("renders English custom driver DSN copy after the module was loaded in another language", async () => {
|
||||
storeState.appearance.uiVersion = "legacy";
|
||||
setCurrentLanguage("zh-CN");
|
||||
|
||||
@@ -9,9 +9,6 @@ import {
|
||||
Checkbox,
|
||||
Select,
|
||||
Alert,
|
||||
Card,
|
||||
Row,
|
||||
Col,
|
||||
Typography,
|
||||
Space,
|
||||
Table,
|
||||
@@ -36,6 +33,7 @@ import {
|
||||
ThunderboltOutlined,
|
||||
DownOutlined,
|
||||
RightOutlined,
|
||||
SearchOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import {
|
||||
getDbIcon,
|
||||
@@ -345,6 +343,7 @@ const ConnectionModal: React.FC<{
|
||||
const [dbType, setDbType] = useState("mysql");
|
||||
const [step, setStep] = useState(1); // 1: Select Type, 2: Configure
|
||||
const [activeGroup, setActiveGroup] = useState(0); // Active category index in step 1
|
||||
const [dbTypeQuery, setDbTypeQuery] = useState("");
|
||||
const [activeConfigSection, setActiveConfigSection] = useState<
|
||||
"basic" | "network" | "appearance"
|
||||
>("basic");
|
||||
@@ -1676,6 +1675,7 @@ const ConnectionModal: React.FC<{
|
||||
setUseHttpTunnel(false);
|
||||
setDbType("mysql");
|
||||
setActiveGroup(0);
|
||||
setDbTypeQuery("");
|
||||
setActiveConfigSection("basic");
|
||||
setActiveNetworkConfig("ssl");
|
||||
setPrimaryPasswordVisible(false);
|
||||
@@ -2356,17 +2356,54 @@ const ConnectionModal: React.FC<{
|
||||
const driverStatusChecking =
|
||||
hasCurrentDriverType && !driverStatusLoaded && step === 2;
|
||||
|
||||
const dbTypeGroups = useMemo(
|
||||
() =>
|
||||
buildConnectionTypeGroups(t).map((group) => ({
|
||||
...group,
|
||||
items: group.items.map((item) => ({
|
||||
...item,
|
||||
icon: getDbIcon(item.key, undefined, 36),
|
||||
})),
|
||||
})),
|
||||
[],
|
||||
);
|
||||
// 翻译函数读取运行时语言;系统语言变化时 preference 仍可能保持 "system",
|
||||
// 因此这组少量目录项必须随组件重渲染重新派生,不能只按 preference 做 memo。
|
||||
const localizedDbTypeGroups = buildConnectionTypeGroups(t).map((group) => ({
|
||||
...group,
|
||||
items: group.items.map((item) => ({
|
||||
...item,
|
||||
icon: getDbIcon(item.key, undefined, 32),
|
||||
})),
|
||||
}));
|
||||
const seenDbTypeKeys = new Set<string>();
|
||||
const allDbTypeItems = localizedDbTypeGroups
|
||||
.flatMap((group) => group.items)
|
||||
.filter((item) => {
|
||||
if (seenDbTypeKeys.has(item.key)) {
|
||||
return false;
|
||||
}
|
||||
seenDbTypeKeys.add(item.key);
|
||||
return true;
|
||||
});
|
||||
const dbTypeGroups = [
|
||||
{
|
||||
labelKey: "connection_modal.step1.group.all",
|
||||
label: t("connection.modal.step1.group.all"),
|
||||
items: allDbTypeItems,
|
||||
},
|
||||
...localizedDbTypeGroups,
|
||||
];
|
||||
|
||||
const normalizedDbTypeQuery = dbTypeQuery.trim().toLowerCase();
|
||||
const visibleDbTypeItems = normalizedDbTypeQuery
|
||||
? (dbTypeGroups[0]?.items ?? []).filter(
|
||||
(item) =>
|
||||
item.name.toLowerCase().includes(normalizedDbTypeQuery) ||
|
||||
String(item.key).toLowerCase().includes(normalizedDbTypeQuery),
|
||||
)
|
||||
: (dbTypeGroups[activeGroup]?.items ?? []);
|
||||
|
||||
const handleDbTypeQueryChange = (value: string) => {
|
||||
setDbTypeQuery(value);
|
||||
if (value.trim()) {
|
||||
setActiveGroup(0);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDbTypeGroupSelect = (index: number) => {
|
||||
setActiveGroup(index);
|
||||
setDbTypeQuery("");
|
||||
};
|
||||
|
||||
const dbTypes = getAllConnectionTypeCatalogItems();
|
||||
|
||||
@@ -2379,20 +2416,46 @@ const ConnectionModal: React.FC<{
|
||||
height: "100%",
|
||||
}}
|
||||
>
|
||||
<div style={{ ...modalInnerSectionStyle, paddingBottom: 12 }}>
|
||||
<div
|
||||
<div
|
||||
style={{
|
||||
...modalInnerSectionStyle,
|
||||
display: "flex",
|
||||
alignItems: "flex-end",
|
||||
justifyContent: "space-between",
|
||||
gap: 24,
|
||||
padding: "8px 12px 14px",
|
||||
flexWrap: "wrap",
|
||||
}}
|
||||
>
|
||||
<div style={{ minWidth: 240, flex: "1 1 360px" }}>
|
||||
<div
|
||||
style={{
|
||||
marginBottom: 5,
|
||||
color: darkMode ? "#f5f7ff" : "#162033",
|
||||
fontSize: 14,
|
||||
fontWeight: 700,
|
||||
}}
|
||||
>
|
||||
{t("connection.modal.step1.sectionTitle")}
|
||||
</div>
|
||||
<div style={modalMutedTextStyle}>
|
||||
{t("connection.modal.step1.sectionDescription")}
|
||||
</div>
|
||||
</div>
|
||||
<Input
|
||||
allowClear
|
||||
aria-label={t("connection.modal.step1.search.placeholder")}
|
||||
value={dbTypeQuery}
|
||||
onChange={(event) => handleDbTypeQueryChange(event.target.value)}
|
||||
placeholder={t("connection.modal.step1.search.placeholder")}
|
||||
prefix={<SearchOutlined style={{ color: overlayTheme.mutedText }} />}
|
||||
style={{
|
||||
marginBottom: 12,
|
||||
color: darkMode ? "#f5f7ff" : "#162033",
|
||||
fontSize: 14,
|
||||
fontWeight: 700,
|
||||
width: "100%",
|
||||
maxWidth: 320,
|
||||
flex: "0 1 320px",
|
||||
}}
|
||||
>
|
||||
{t("connection.modal.step1.sectionTitle")}
|
||||
</div>
|
||||
<div style={modalMutedTextStyle}>
|
||||
{t("connection.modal.step1.sectionDescription")}
|
||||
</div>
|
||||
{...noAutoCapInputProps}
|
||||
/>
|
||||
</div>
|
||||
{typeSelectWarning && (
|
||||
<Alert
|
||||
@@ -2423,42 +2486,55 @@ const ConnectionModal: React.FC<{
|
||||
display: "flex",
|
||||
flex: 1,
|
||||
minHeight: 0,
|
||||
padding: 12,
|
||||
padding: "2px 12px 8px",
|
||||
}}
|
||||
>
|
||||
{/* 左侧分类导航 */}
|
||||
<div
|
||||
role="navigation"
|
||||
aria-label={t("connection.modal.step1.sectionTitle")}
|
||||
style={{
|
||||
width: 148,
|
||||
width: 142,
|
||||
borderRight: `1px solid ${step1SidebarDividerColor}`,
|
||||
paddingRight: 10,
|
||||
paddingRight: 12,
|
||||
flexShrink: 0,
|
||||
overflowY: "auto",
|
||||
}}
|
||||
>
|
||||
{dbTypeGroups.map((group, idx) => (
|
||||
<div
|
||||
key={group.label}
|
||||
onClick={() => setActiveGroup(idx)}
|
||||
<button
|
||||
key={group.labelKey}
|
||||
type="button"
|
||||
className="gn-connection-type-group"
|
||||
data-connection-group-key={group.labelKey}
|
||||
aria-pressed={activeGroup === idx}
|
||||
onClick={() => handleDbTypeGroupSelect(idx)}
|
||||
style={{
|
||||
padding: "11px 12px",
|
||||
width: "100%",
|
||||
padding: "8px 10px",
|
||||
cursor: "pointer",
|
||||
borderRadius: 12,
|
||||
marginBottom: 6,
|
||||
border: "none",
|
||||
borderRadius: 0,
|
||||
borderLeft: `2px solid ${
|
||||
activeGroup === idx ? step1SidebarActiveColor : "transparent"
|
||||
}`,
|
||||
marginBottom: 2,
|
||||
background:
|
||||
activeGroup === idx ? step1SidebarActiveBg : "transparent",
|
||||
activeGroup === idx
|
||||
? `linear-gradient(90deg, ${step1SidebarActiveBg}, transparent)`
|
||||
: "transparent",
|
||||
color:
|
||||
activeGroup === idx ? step1SidebarActiveColor : undefined,
|
||||
fontWeight: activeGroup === idx ? 700 : 500,
|
||||
transition: "all 0.2s",
|
||||
transition: "background 120ms ease, color 120ms ease",
|
||||
fontSize: 13,
|
||||
fontFamily: "var(--gn-font-sans)",
|
||||
textAlign: "left",
|
||||
}}
|
||||
>
|
||||
{group.label}
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
{/* 右侧数据源卡片 */}
|
||||
<div
|
||||
style={{
|
||||
flex: 1,
|
||||
@@ -2468,72 +2544,98 @@ const ConnectionModal: React.FC<{
|
||||
overflowX: "hidden",
|
||||
}}
|
||||
>
|
||||
<Row gutter={[14, 14]}>
|
||||
{dbTypeGroups[activeGroup]?.items.map((item) => (
|
||||
<Col span={12} key={item.key}>
|
||||
<Card
|
||||
hoverable
|
||||
onClick={() => {
|
||||
void handleTypeSelect(item.key);
|
||||
}}
|
||||
<div
|
||||
style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "repeat(auto-fit, minmax(220px, 1fr))",
|
||||
gap: "2px 12px",
|
||||
}}
|
||||
>
|
||||
{visibleDbTypeItems.map((item) => (
|
||||
<button
|
||||
key={item.key}
|
||||
type="button"
|
||||
data-connection-type-key={item.key}
|
||||
aria-label={item.name}
|
||||
onClick={() => {
|
||||
void handleTypeSelect(item.key);
|
||||
}}
|
||||
className="gn-connection-type-row"
|
||||
style={{
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: 10,
|
||||
minHeight: 44,
|
||||
padding: "6px 8px",
|
||||
border: "none",
|
||||
borderRadius: 6,
|
||||
color: "inherit",
|
||||
cursor: "pointer",
|
||||
fontFamily: "var(--gn-font-sans)",
|
||||
textAlign: "left",
|
||||
transition: "background 120ms ease",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
cursor: "pointer",
|
||||
minHeight: 92,
|
||||
borderRadius: 16,
|
||||
border: darkMode
|
||||
? "1px solid rgba(255,255,255,0.08)"
|
||||
: "1px solid rgba(16,24,40,0.08)",
|
||||
background: darkMode
|
||||
? "rgba(255,255,255,0.03)"
|
||||
: "rgba(255,255,255,0.80)",
|
||||
}}
|
||||
styles={{
|
||||
body: {
|
||||
padding: 14,
|
||||
display: "flex",
|
||||
alignItems: "flex-start",
|
||||
gap: 12,
|
||||
height: "100%",
|
||||
},
|
||||
width: 32,
|
||||
height: 32,
|
||||
display: "grid",
|
||||
placeItems: "center",
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
{item.icon}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
minWidth: 0,
|
||||
flex: 1,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 2,
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
strong
|
||||
style={{
|
||||
width: 44,
|
||||
height: 44,
|
||||
borderRadius: 14,
|
||||
display: "grid",
|
||||
placeItems: "center",
|
||||
flexShrink: 0,
|
||||
background: darkMode
|
||||
? "rgba(255,255,255,0.05)"
|
||||
: "rgba(22,119,255,0.08)",
|
||||
fontSize: 13.5,
|
||||
whiteSpace: "nowrap",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
maxWidth: "100%",
|
||||
display: "block",
|
||||
lineHeight: 1.2,
|
||||
}}
|
||||
>
|
||||
{item.icon}
|
||||
</div>
|
||||
<div style={{ minWidth: 0, flex: 1 }}>
|
||||
<Text
|
||||
strong
|
||||
style={{
|
||||
fontSize: 14,
|
||||
whiteSpace: "nowrap",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
maxWidth: "100%",
|
||||
display: "block",
|
||||
}}
|
||||
>
|
||||
{item.name}
|
||||
</Text>
|
||||
<Text type="secondary" style={{ fontSize: 12 }}>
|
||||
{getConnectionTypeHint(item.key, t)}
|
||||
</Text>
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
{item.name}
|
||||
</Text>
|
||||
<Text
|
||||
type="secondary"
|
||||
style={{
|
||||
fontSize: 11.5,
|
||||
display: "block",
|
||||
whiteSpace: "nowrap",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
lineHeight: 1.2,
|
||||
}}
|
||||
>
|
||||
{getConnectionTypeHint(item.key, t)}
|
||||
</Text>
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</Row>
|
||||
</div>
|
||||
{normalizedDbTypeQuery && visibleDbTypeItems.length === 0 ? (
|
||||
<div
|
||||
role="status"
|
||||
style={{ ...modalMutedTextStyle, padding: "18px 10px" }}
|
||||
>
|
||||
{t("connection.modal.step1.search.empty")}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -17,6 +17,10 @@ type LocalizedExpectation = {
|
||||
};
|
||||
|
||||
const remainingConnectionModalSliceExpectations: LocalizedExpectation[] = [
|
||||
{
|
||||
key: "connection.modal.step1.group.all",
|
||||
catalogKey: "connection_modal.step1.group.all",
|
||||
},
|
||||
{
|
||||
key: "connection.modal.step1.group.relational",
|
||||
catalogKey: "connection_modal.step1.group.relational",
|
||||
@@ -69,6 +73,14 @@ const remainingConnectionModalSliceExpectations: LocalizedExpectation[] = [
|
||||
key: "connection.modal.step1.hint.standard",
|
||||
catalogKey: "connection_modal.step1.hint.standard",
|
||||
},
|
||||
{
|
||||
key: "connection.modal.step1.search.empty",
|
||||
catalogKey: "connection_modal.step1.search.empty",
|
||||
},
|
||||
{
|
||||
key: "connection.modal.step1.search.placeholder",
|
||||
catalogKey: "connection_modal.step1.search.placeholder",
|
||||
},
|
||||
{
|
||||
key: "connection.modal.field.driver.label",
|
||||
catalogKey: "connection_modal.field.driver_name",
|
||||
|
||||
@@ -3767,6 +3767,7 @@
|
||||
"connection_modal.status.unhealthy": "Fehlerhaft",
|
||||
"connection_modal.step.select_source": "Datenquelle auswählen",
|
||||
"connection_modal.step.select_source_description": "Wählen Sie den zu erstellenden Verbindungstyp.",
|
||||
"connection_modal.step1.group.all": "Alle",
|
||||
"connection_modal.step1.group.domestic": "Inländische Datenbanken",
|
||||
"connection_modal.step1.group.message_queue": "Nachrichtenwarteschlangen",
|
||||
"connection_modal.step1.group.nosql": "NoSQL-Datenbanken",
|
||||
@@ -3786,6 +3787,8 @@
|
||||
"connection_modal.step1.hint.qdrant": "Collection-Browsing, Vektorsuche und Payload-Filter",
|
||||
"connection_modal.step1.hint.redis": "Einzelknoten / Cluster",
|
||||
"connection_modal.step1.hint.standard": "Standard-Verbindungskonfiguration",
|
||||
"connection_modal.step1.search.empty": "Keine passende Datenquelle",
|
||||
"connection_modal.step1.search.placeholder": "Datenquellennamen suchen",
|
||||
"connection_modal.switch.off": "Aus",
|
||||
"connection_modal.switch.on": "Ein",
|
||||
"connection_modal.table.health": "Zustand",
|
||||
|
||||
@@ -3767,6 +3767,7 @@
|
||||
"connection_modal.status.unhealthy": "Unhealthy",
|
||||
"connection_modal.step.select_source": "Select data source",
|
||||
"connection_modal.step.select_source_description": "Choose the connection type to create.",
|
||||
"connection_modal.step1.group.all": "All",
|
||||
"connection_modal.step1.group.domestic": "Domestic databases",
|
||||
"connection_modal.step1.group.message_queue": "Message queues",
|
||||
"connection_modal.step1.group.nosql": "NoSQL databases",
|
||||
@@ -3786,6 +3787,8 @@
|
||||
"connection_modal.step1.hint.qdrant": "Collection browsing, vector search, and Payload filtering",
|
||||
"connection_modal.step1.hint.redis": "Single node / cluster",
|
||||
"connection_modal.step1.hint.standard": "Standard connection configuration",
|
||||
"connection_modal.step1.search.empty": "No matching data source",
|
||||
"connection_modal.step1.search.placeholder": "Search data source name",
|
||||
"connection_modal.switch.off": "Off",
|
||||
"connection_modal.switch.on": "On",
|
||||
"connection_modal.table.health": "Health",
|
||||
|
||||
@@ -3767,6 +3767,7 @@
|
||||
"connection_modal.status.unhealthy": "異常",
|
||||
"connection_modal.step.select_source": "データソースを選択",
|
||||
"connection_modal.step.select_source_description": "作成する接続タイプを選択します。",
|
||||
"connection_modal.step1.group.all": "すべて",
|
||||
"connection_modal.step1.group.domestic": "国産データベース",
|
||||
"connection_modal.step1.group.message_queue": "メッセージキュー",
|
||||
"connection_modal.step1.group.nosql": "NoSQL データベース",
|
||||
@@ -3786,6 +3787,8 @@
|
||||
"connection_modal.step1.hint.qdrant": "Collection 閲覧、ベクトル検索、Payload フィルター",
|
||||
"connection_modal.step1.hint.redis": "シングルノード / クラスター",
|
||||
"connection_modal.step1.hint.standard": "標準接続設定",
|
||||
"connection_modal.step1.search.empty": "一致するデータソースがありません",
|
||||
"connection_modal.step1.search.placeholder": "データソース名を検索",
|
||||
"connection_modal.switch.off": "オフ",
|
||||
"connection_modal.switch.on": "オン",
|
||||
"connection_modal.table.health": "ヘルス",
|
||||
|
||||
@@ -3767,6 +3767,7 @@
|
||||
"connection_modal.status.unhealthy": "Неисправен",
|
||||
"connection_modal.step.select_source": "Выбор источника данных",
|
||||
"connection_modal.step.select_source_description": "Выберите тип создаваемого подключения.",
|
||||
"connection_modal.step1.group.all": "Все",
|
||||
"connection_modal.step1.group.domestic": "Отечественные базы данных",
|
||||
"connection_modal.step1.group.message_queue": "Очереди сообщений",
|
||||
"connection_modal.step1.group.nosql": "Базы данных NoSQL",
|
||||
@@ -3786,6 +3787,8 @@
|
||||
"connection_modal.step1.hint.qdrant": "Просмотр Collection, векторный поиск и фильтрация Payload",
|
||||
"connection_modal.step1.hint.redis": "Одиночный узел / кластер",
|
||||
"connection_modal.step1.hint.standard": "Стандартная конфигурация подключения",
|
||||
"connection_modal.step1.search.empty": "Подходящих источников нет",
|
||||
"connection_modal.step1.search.placeholder": "Поиск по названию источника",
|
||||
"connection_modal.switch.off": "Выкл",
|
||||
"connection_modal.switch.on": "Вкл",
|
||||
"connection_modal.table.health": "Состояние",
|
||||
|
||||
@@ -3767,6 +3767,7 @@
|
||||
"connection_modal.status.unhealthy": "异常",
|
||||
"connection_modal.step.select_source": "选择数据源",
|
||||
"connection_modal.step.select_source_description": "选择要创建的连接类型。",
|
||||
"connection_modal.step1.group.all": "全部",
|
||||
"connection_modal.step1.group.domestic": "国产数据库",
|
||||
"connection_modal.step1.group.message_queue": "消息队列",
|
||||
"connection_modal.step1.group.nosql": "NoSQL 数据库",
|
||||
@@ -3786,6 +3787,8 @@
|
||||
"connection_modal.step1.hint.qdrant": "Collection 浏览、向量搜索和 Payload 过滤",
|
||||
"connection_modal.step1.hint.redis": "单机 / 集群",
|
||||
"connection_modal.step1.hint.standard": "标准连接配置",
|
||||
"connection_modal.step1.search.empty": "没有匹配的数据源",
|
||||
"connection_modal.step1.search.placeholder": "搜索数据源名称",
|
||||
"connection_modal.switch.off": "关",
|
||||
"connection_modal.switch.on": "开",
|
||||
"connection_modal.table.health": "健康",
|
||||
|
||||
@@ -3767,6 +3767,7 @@
|
||||
"connection_modal.status.unhealthy": "異常",
|
||||
"connection_modal.step.select_source": "選擇資料來源",
|
||||
"connection_modal.step.select_source_description": "選擇要建立的連線類型。",
|
||||
"connection_modal.step1.group.all": "全部",
|
||||
"connection_modal.step1.group.domestic": "國產資料庫",
|
||||
"connection_modal.step1.group.message_queue": "訊息佇列",
|
||||
"connection_modal.step1.group.nosql": "NoSQL 資料庫",
|
||||
@@ -3786,6 +3787,8 @@
|
||||
"connection_modal.step1.hint.qdrant": "Collection 瀏覽、向量搜尋與 Payload 過濾",
|
||||
"connection_modal.step1.hint.redis": "單機 / 叢集",
|
||||
"connection_modal.step1.hint.standard": "標準連線設定",
|
||||
"connection_modal.step1.search.empty": "沒有符合的資料來源",
|
||||
"connection_modal.step1.search.placeholder": "搜尋資料來源名稱",
|
||||
"connection_modal.switch.off": "關",
|
||||
"connection_modal.switch.on": "開",
|
||||
"connection_modal.table.health": "健康",
|
||||
|
||||
Reference in New Issue
Block a user