feat(core): implement Kintone Customize Manager core features

Wave 1 - Foundation:
- Add shared TypeScript type definitions (domain, kintone, version, ipc)
- Implement storage module with safeStorage encryption
- Implement Kintone REST API client with authentication
- Add IPC handlers for all features
- Expose APIs via preload contextBridge
- Setup Zustand stores for state management

Wave 2 - Domain Management:
- Implement Domain store with IPC actions
- Create DomainManager, DomainList, DomainForm components
- Connect UI components to store

Wave 3 - Resource Browsing:
- Create SpaceTree component for browsing spaces/apps
- Create AppDetail component for app configuration view
- Create CodeViewer component with syntax highlighting

Wave 4 - Deployment:
- Create FileUploader drag-and-drop component
- Create DeployDialog with step-by-step deployment flow
- Implement deployment IPC handler with auto-backup

Wave 5 - Version Management:
- Create VersionHistory component
- Implement version storage and rollback logic

Wave 6 - Integration:
- Integrate all components into main App layout
- Update main process entry point
- Configure TypeScript paths for renderer imports
This commit is contained in:
2026-03-12 11:03:48 +08:00
parent ab7e9b597a
commit da7f566ecf
36 changed files with 5847 additions and 151 deletions

View File

@@ -0,0 +1,210 @@
/**
* 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();
if (isEdit && editingDomain) {
const params: UpdateDomainParams = {
id: domainId,
name: values.name,
domain: values.domain,
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: values.name,
domain: values.domain,
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);
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="名称"
rules={[{ required: true, message: "请输入名称" }]}
>
<Input placeholder="例如:生产环境" />
</Form.Item>
<Form.Item
name="domain"
label="Kintone 域名"
rules={[
{ required: true, message: "请输入域名" },
{
pattern: /^[\w.-]+$/,
message: "请输入有效的域名例如company.kintone.com",
},
]}
>
<Input placeholder="例如company.kintone.com" />
</Form.Item>
<Form.Item
name="username"
label="用户名"
rules={[
{ required: true, message: "请输入用户名" },
{ type: "email", 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 type="primary" onClick={handleSubmit} loading={loading}>
{isEdit ? "更新" : "创建"}
</Button>
</Space>
</Form.Item>
</Form>
</Modal>
);
};
export default DomainForm;