- Make name field optional (defaults to domain if empty) - Simplify domain placeholder and error messages - Add test connection button for credential validation before save
275 lines
8.1 KiB
TypeScript
275 lines
8.1 KiB
TypeScript
/**
|
|
* DomainForm Component
|
|
* Dialog for creating and editing domain configurations
|
|
*/
|
|
|
|
import React from "react";
|
|
import { Modal, Form, Input, Select, Button, Space, message } from "antd";
|
|
import { createStyles } from "antd-style";
|
|
import { useDomainStore } from "@renderer/stores";
|
|
import type { Domain } from "@renderer/types/domain";
|
|
import type {
|
|
CreateDomainParams,
|
|
UpdateDomainParams,
|
|
} from "@renderer/types/ipc";
|
|
|
|
const useStyles = createStyles(({ token, css }) => ({
|
|
form: css`
|
|
.ant-form-item {
|
|
margin-bottom: ${token.marginMD}px;
|
|
}
|
|
`,
|
|
passwordHint: css`
|
|
color: ${token.colorTextSecondary};
|
|
font-size: ${token.fontSizeSM}px;
|
|
margin-top: ${token.marginXXS}px;
|
|
`,
|
|
}));
|
|
|
|
interface DomainFormProps {
|
|
open: boolean;
|
|
onClose: () => void;
|
|
domainId: string | null;
|
|
}
|
|
|
|
const DomainForm: React.FC<DomainFormProps> = ({ open, onClose, domainId }) => {
|
|
const { styles } = useStyles();
|
|
const [form] = Form.useForm();
|
|
const { domains, createDomain, updateDomainById, loading } = useDomainStore();
|
|
|
|
const isEdit = !!domainId;
|
|
const editingDomain = domainId
|
|
? domains.find((d) => d.id === domainId)
|
|
: null;
|
|
|
|
// Reset form when dialog opens
|
|
React.useEffect(() => {
|
|
if (open) {
|
|
if (editingDomain) {
|
|
form.setFieldsValue({
|
|
name: editingDomain.name,
|
|
domain: editingDomain.domain,
|
|
username: editingDomain.username,
|
|
authType: editingDomain.authType,
|
|
apiToken: editingDomain.apiToken || "",
|
|
});
|
|
} else {
|
|
form.resetFields();
|
|
form.setFieldsValue({ authType: "password" });
|
|
}
|
|
}
|
|
}, [open, editingDomain, form]);
|
|
|
|
const handleSubmit = async () => {
|
|
try {
|
|
const values = await form.validateFields();
|
|
|
|
// Process domain: remove protocol prefix and trailing slashes
|
|
let processedDomain = values.domain.trim();
|
|
if (processedDomain.startsWith("https://")) {
|
|
processedDomain = processedDomain.slice(8);
|
|
} else if (processedDomain.startsWith("http://")) {
|
|
processedDomain = processedDomain.slice(7);
|
|
}
|
|
processedDomain = processedDomain.replace(/\/+$/, "");
|
|
|
|
// Use domain as name if name is empty
|
|
const name = values.name?.trim() || processedDomain;
|
|
|
|
if (isEdit && editingDomain) {
|
|
const params: UpdateDomainParams = {
|
|
id: domainId,
|
|
name,
|
|
domain: processedDomain,
|
|
username: values.username,
|
|
authType: values.authType,
|
|
apiToken:
|
|
values.authType === "api_token" ? values.apiToken : undefined,
|
|
};
|
|
|
|
// Only include password if provided
|
|
if (values.password) {
|
|
params.password = values.password;
|
|
}
|
|
|
|
const success = await updateDomainById(params);
|
|
if (success) {
|
|
message.success("Domain 更新成功");
|
|
onClose();
|
|
} else {
|
|
message.error("更新失败");
|
|
}
|
|
} else {
|
|
const params: CreateDomainParams = {
|
|
name,
|
|
domain: processedDomain,
|
|
username: values.username,
|
|
password: values.password,
|
|
authType: values.authType,
|
|
apiToken:
|
|
values.authType === "api_token" ? values.apiToken : undefined,
|
|
};
|
|
|
|
const success = await createDomain(params);
|
|
if (success) {
|
|
message.success("Domain 创建成功");
|
|
onClose();
|
|
} else {
|
|
message.error("创建失败");
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error("Form validation failed:", error);
|
|
}
|
|
};
|
|
|
|
const authType = Form.useWatch("authType", form);
|
|
const [testing, setTesting] = React.useState(false);
|
|
|
|
// Test connection with current form values
|
|
const handleTestConnection = async () => {
|
|
try {
|
|
const values = await form.validateFields([
|
|
"domain",
|
|
"username",
|
|
"authType",
|
|
"password",
|
|
"apiToken",
|
|
]);
|
|
|
|
// Process domain
|
|
let processedDomain = values.domain.trim();
|
|
if (processedDomain.startsWith("https://")) {
|
|
processedDomain = processedDomain.slice(8);
|
|
} else if (processedDomain.startsWith("http://")) {
|
|
processedDomain = processedDomain.slice(7);
|
|
}
|
|
processedDomain = processedDomain.replace(/\/+$/, "");
|
|
|
|
setTesting(true);
|
|
const result = await window.api.testDomainConnection({
|
|
domain: processedDomain,
|
|
username: values.username,
|
|
authType: values.authType,
|
|
password: values.authType === "password" ? values.password : undefined,
|
|
apiToken: values.authType === "api_token" ? values.apiToken : undefined,
|
|
});
|
|
|
|
if (result.success) {
|
|
message.success("连接成功");
|
|
} else {
|
|
message.error(result.error || "连接失败");
|
|
}
|
|
} catch (error) {
|
|
console.error("Test connection failed:", error);
|
|
} finally {
|
|
setTesting(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Modal
|
|
title={isEdit ? "编辑 Domain" : "添加 Domain"}
|
|
open={open}
|
|
onCancel={onClose}
|
|
footer={null}
|
|
width={520}
|
|
destroyOnClose
|
|
>
|
|
<Form
|
|
form={form}
|
|
layout="vertical"
|
|
className={styles.form}
|
|
initialValues={{ authType: "password" }}
|
|
>
|
|
<Form.Item name="name" label="名称" style={{ marginTop: 8 }}>
|
|
<Input placeholder="可选,留空则使用域名" />
|
|
|
|
<Form.Item
|
|
name="domain"
|
|
label="Kintone 域名"
|
|
rules={[
|
|
{ required: true, message: "请输入域名" },
|
|
{
|
|
validator: (_, value) => {
|
|
if (!value) return Promise.resolve();
|
|
// Allow https:// or http:// prefix
|
|
let domain = value.trim();
|
|
if (domain.startsWith("https://")) {
|
|
domain = domain.slice(8);
|
|
} else if (domain.startsWith("http://")) {
|
|
domain = domain.slice(7);
|
|
}
|
|
// Remove trailing slashes
|
|
domain = domain.replace(/\/+$/, "");
|
|
// Validate domain format
|
|
if (/^[\w.-]+$/.test(domain)) {
|
|
return Promise.resolve();
|
|
}
|
|
return Promise.reject(new Error("请输入有效的域名"));
|
|
},
|
|
},
|
|
]}
|
|
>
|
|
<Input placeholder="https://company.kintone.com" />
|
|
</Form.Item>
|
|
|
|
<Form.Item
|
|
name="username"
|
|
label="用户名"
|
|
rules={[{ required: true, message: "请输入用户名" }]}
|
|
>
|
|
<Input placeholder="登录 Kintone 的用户名" />
|
|
</Form.Item>
|
|
|
|
<Form.Item
|
|
name="authType"
|
|
label="认证方式"
|
|
rules={[{ required: true }]}
|
|
>
|
|
<Select>
|
|
<Select.Option value="password">密码认证</Select.Option>
|
|
<Select.Option value="api_token">API Token 认证</Select.Option>
|
|
</Select>
|
|
</Form.Item>
|
|
|
|
{authType === "password" && (
|
|
<Form.Item
|
|
name="password"
|
|
label="密码"
|
|
rules={isEdit ? [] : [{ required: true, message: "请输入密码" }]}
|
|
>
|
|
<Input.Password
|
|
placeholder={isEdit ? "留空则保持原密码" : "请输入密码"}
|
|
/>
|
|
</Form.Item>
|
|
)}
|
|
|
|
{authType === "api_token" && (
|
|
<Form.Item
|
|
name="apiToken"
|
|
label="API Token"
|
|
rules={[{ required: true, message: "请输入 API Token" }]}
|
|
>
|
|
<Input placeholder="从 Kintone 设置中获取 API Token" />
|
|
</Form.Item>
|
|
)}
|
|
|
|
<Form.Item style={{ marginTop: 24, marginBottom: 0 }}>
|
|
<Space style={{ width: "100%", justifyContent: "flex-end" }}>
|
|
<Button onClick={onClose}>取消</Button>
|
|
<Button onClick={handleTestConnection} loading={testing}>
|
|
测试连接
|
|
</Button>
|
|
<Button type="primary" onClick={handleSubmit} loading={loading}>
|
|
{isEdit ? "更新" : "创建"}
|
|
</Button>
|
|
</Space>
|
|
</Form.Item>
|
|
</Form>
|
|
</Modal>
|
|
);
|
|
};
|
|
|
|
export default DomainForm;
|