refactor: reuse notify config controls

This commit is contained in:
ShukeBta
2026-06-24 23:47:51 +08:00
parent 92b5fbf5b5
commit 326d7f2eba
2 changed files with 255 additions and 176 deletions
@@ -0,0 +1,97 @@
import { Field } from './NotifyChannelFormField'
type ConfigControlProps = {
config: Record<string, string>
updateConfig: (key: string, value: string) => void
}
type ConfigInputProps = ConfigControlProps & {
label: string
name: string
placeholder?: string
required?: boolean
type?: string
}
export function ConfigInput({
config,
updateConfig,
label,
name,
placeholder,
required,
type,
}: ConfigInputProps) {
return (
<Field label={label}>
<input
required={required}
type={type}
className="input-base"
placeholder={placeholder}
value={config[name] ?? ''}
onChange={(event) => updateConfig(name, event.target.value)}
/>
</Field>
)
}
type ConfigSelectProps = ConfigControlProps & {
label: string
name: string
options: Array<{ value: string; label: string }>
defaultValue?: string
}
export function ConfigSelect({
config,
updateConfig,
label,
name,
options,
defaultValue = '',
}: ConfigSelectProps) {
return (
<Field label={label}>
<select
className="input-base"
value={config[name] ?? defaultValue}
onChange={(event) => updateConfig(name, event.target.value)}
>
{options.map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
</Field>
)
}
type ConfigTextareaProps = ConfigControlProps & {
label: string
name: string
placeholder?: string
rows: number
}
export function ConfigTextarea({
config,
updateConfig,
label,
name,
placeholder,
rows,
}: ConfigTextareaProps) {
return (
<Field label={label}>
<textarea
rows={rows}
className="input-base font-mono text-xs"
placeholder={placeholder}
value={config[name] ?? ''}
onChange={(event) => updateConfig(name, event.target.value)}
/>
</Field>
)
}
+158 -176
View File
@@ -1,63 +1,67 @@
import { Field } from './NotifyChannelFormField' import {
ConfigInput,
ConfigSelect,
ConfigTextarea,
} from './NotifyChannelConfigControls'
type ProviderFieldsProps = { type ProviderFieldsProps = {
config: Record<string, string> config: Record<string, string>
updateConfig: (key: string, value: string) => void updateConfig: (key: string, value: string) => void
} }
const METHOD_OPTIONS = [
{ value: 'POST', label: 'POST' },
{ value: 'GET', label: 'GET' },
]
const TLS_OPTIONS = [
{ value: 'true', label: '启用' },
{ value: 'false', label: '关闭' },
]
export function TelegramFields({ config, updateConfig }: ProviderFieldsProps) { export function TelegramFields({ config, updateConfig }: ProviderFieldsProps) {
const control = { config, updateConfig }
return ( return (
<> <>
<Field label="Bot Token"> <ConfigInput
<input {...control}
required required
className="input-base" label="Bot Token"
placeholder="123456:ABC-DEF…" name="bot_token"
value={config.bot_token ?? ''} placeholder="123456:ABC-DEF…"
onChange={(e) => updateConfig('bot_token', e.target.value)} />
/> <ConfigInput
</Field> {...control}
<Field label="管理员 Telegram ID"> required
<input label="管理员 Telegram ID"
required name="admin_user_ids"
className="input-base" placeholder="多个用逗号分隔,如 123456789,987654321"
placeholder="多个用逗号分隔,如 123456789,987654321" />
value={config.admin_user_ids ?? ''} <ConfigInput
onChange={(e) => updateConfig('admin_user_ids', e.target.value)} {...control}
/> label="绑定群组 ID"
</Field> name="group_chat_id"
<Field label="绑定群组 ID"> placeholder="选填,如 -1001234567890;填写后群组成员可唤醒/绑定"
<input />
className="input-base" <ConfigInput
placeholder="选填,如 -1001234567890;填写后群组成员可唤醒/绑定" {...control}
value={config.group_chat_id ?? ''} label="绑定频道 ID"
onChange={(e) => updateConfig('group_chat_id', e.target.value)} name="channel_chat_id"
/> placeholder="选填,如 -1009876543210;填写后频道成员可唤醒/绑定"
</Field> />
<Field label="绑定频道 ID"> <ConfigInput
<input {...control}
className="input-base" label="Telegram API 地址 (可选)"
placeholder="选填,如 -1009876543210;填写后频道成员可唤醒/绑定" name="api_base_url"
value={config.channel_chat_id ?? ''} placeholder="默认 https://api.telegram.org;反代可填 https://tg.example.com"
onChange={(e) => updateConfig('channel_chat_id', e.target.value)} />
/> <ConfigInput
</Field> {...control}
<Field label="Telegram API 地址 (可选)"> label="Telegram 代理 (可选)"
<input name="proxy_url"
className="input-base" placeholder="如 http://172.17.0.1:7890 或 socks5://172.17.0.1:1080"
placeholder="默认 https://api.telegram.org;反代可填 https://tg.example.com" />
value={config.api_base_url ?? ''}
onChange={(e) => updateConfig('api_base_url', e.target.value)}
/>
</Field>
<Field label="Telegram 代理 (可选)">
<input
className="input-base"
placeholder="如 http://172.17.0.1:7890 或 socks5://172.17.0.1:1080"
value={config.proxy_url ?? ''}
onChange={(e) => updateConfig('proxy_url', e.target.value)}
/>
</Field>
<div className="rounded-2xl border border-primary-400/15 bg-primary-400/5 px-4 py-3 text-xs leading-6 text-ink-50"> <div className="rounded-2xl border border-primary-400/15 bg-primary-400/5 px-4 py-3 text-xs leading-6 text-ink-50">
群组 ID、频道 ID 均为选填,可填一个、两个都填,也可以不填。管理功能始终仅管理员 Telegram ID 或已绑定的本地管理员可用;普通用户需要在已配置的群组或频道中,才能使用 <code>/start 用户名 密码</code> 绑定账号、切换隐藏成人媒体库和目录。不配置群组/频道时,普通用户不会被放行。若测试通知超时,可填写反代 API 地址或代理地址。 群组 ID、频道 ID 均为选填,可填一个、两个都填,也可以不填。管理功能始终仅管理员 Telegram ID 或已绑定的本地管理员可用;普通用户需要在已配置的群组或频道中,才能使用 <code>/start 用户名 密码</code> 绑定账号、切换隐藏成人媒体库和目录。不配置群组/频道时,普通用户不会被放行。若测试通知超时,可填写反代 API 地址或代理地址。
</div> </div>
@@ -66,156 +70,134 @@ export function TelegramFields({ config, updateConfig }: ProviderFieldsProps) {
} }
export function WechatFields({ config, updateConfig }: ProviderFieldsProps) { export function WechatFields({ config, updateConfig }: ProviderFieldsProps) {
const control = { config, updateConfig }
return ( return (
<Field label="SendKey"> <ConfigInput
<input {...control}
required required
className="input-base" label="SendKey"
placeholder="SCT…" name="sendkey"
value={config.sendkey ?? ''} placeholder="SCT…"
onChange={(e) => updateConfig('sendkey', e.target.value)} />
/>
</Field>
) )
} }
export function BarkFields({ config, updateConfig }: ProviderFieldsProps) { export function BarkFields({ config, updateConfig }: ProviderFieldsProps) {
const control = { config, updateConfig }
return ( return (
<> <>
<Field label="设备 Key"> <ConfigInput
<input {...control}
required required
className="input-base" label="设备 Key"
value={config.device_key ?? ''} name="device_key"
onChange={(e) => updateConfig('device_key', e.target.value)} />
/> <ConfigInput
</Field> {...control}
<Field label="服务器地址 (可选)"> label="服务器地址 (可选)"
<input name="server"
className="input-base" placeholder="https://api.day.app"
placeholder="https://api.day.app" />
value={config.server ?? ''}
onChange={(e) => updateConfig('server', e.target.value)}
/>
</Field>
</> </>
) )
} }
export function WebhookFields({ config, updateConfig }: ProviderFieldsProps) { export function WebhookFields({ config, updateConfig }: ProviderFieldsProps) {
const control = { config, updateConfig }
return ( return (
<> <>
<Field label="URL"> <ConfigInput
<input {...control}
required required
className="input-base" label="URL"
placeholder="https://example.com/notify" name="url"
value={config.url ?? ''} placeholder="https://example.com/notify"
onChange={(e) => updateConfig('url', e.target.value)} />
/> <ConfigSelect
</Field> {...control}
<Field label="Method"> label="Method"
<select name="method"
className="input-base" options={METHOD_OPTIONS}
value={config.method ?? 'POST'} defaultValue="POST"
onChange={(e) => updateConfig('method', e.target.value)} />
> <ConfigTextarea
<option value="POST">POST</option> {...control}
<option value="GET">GET</option> label="Headers (JSON)"
</select> name="headers"
</Field> rows={2}
<Field label="Headers (JSON)"> placeholder='{"Content-Type":"application/json"}'
<textarea />
rows={2} <ConfigTextarea
className="input-base font-mono text-xs" {...control}
placeholder='{"Content-Type":"application/json"}' label="Body 模板 (支持 {{title}} {{message}})"
value={config.headers ?? ''} name="body_template"
onChange={(e) => updateConfig('headers', e.target.value)} rows={3}
/> placeholder='{"title":"{{title}}","message":"{{message}}"}'
</Field> />
<Field label="Body 模板 (支持 {{title}} {{message}})">
<textarea
rows={3}
className="input-base font-mono text-xs"
placeholder='{"title":"{{title}}","message":"{{message}}"}'
value={config.body_template ?? ''}
onChange={(e) => updateConfig('body_template', e.target.value)}
/>
</Field>
</> </>
) )
} }
export function EmailFields({ config, updateConfig }: ProviderFieldsProps) { export function EmailFields({ config, updateConfig }: ProviderFieldsProps) {
const control = { config, updateConfig }
return ( return (
<> <>
<Field label="SMTP 地址"> <ConfigInput
<input {...control}
required required
className="input-base" label="SMTP 地址"
placeholder="smtp.gmail.com" name="smtp_host"
value={config.smtp_host ?? ''} placeholder="smtp.gmail.com"
onChange={(e) => updateConfig('smtp_host', e.target.value)} />
/>
</Field>
<div className="grid grid-cols-2 gap-3"> <div className="grid grid-cols-2 gap-3">
<Field label="SMTP 端口"> <ConfigInput
<input {...control}
required required
className="input-base" label="SMTP 端口"
placeholder="465" name="smtp_port"
value={config.smtp_port ?? ''} placeholder="465"
onChange={(e) => updateConfig('smtp_port', e.target.value)} />
/> <ConfigSelect
</Field> {...control}
<Field label="TLS"> label="TLS"
<select name="tls"
className="input-base" options={TLS_OPTIONS}
value={config.tls ?? 'true'} defaultValue="true"
onChange={(e) => updateConfig('tls', e.target.value)} />
>
<option value="true">启用</option>
<option value="false">关闭</option>
</select>
</Field>
</div> </div>
<div className="grid grid-cols-2 gap-3"> <div className="grid grid-cols-2 gap-3">
<Field label="用户名"> <ConfigInput
<input {...control}
required required
className="input-base" label="用户名"
value={config.username ?? ''} name="username"
onChange={(e) => updateConfig('username', e.target.value)} />
/> <ConfigInput
</Field> {...control}
<Field label="密码"> required
<input label="密码"
type="password" name="password"
required type="password"
className="input-base" />
value={config.password ?? ''}
onChange={(e) => updateConfig('password', e.target.value)}
/>
</Field>
</div> </div>
<Field label="发件人"> <ConfigInput
<input {...control}
required required
className="input-base" label="发件人"
placeholder="noreply@example.com" name="from"
value={config.from ?? ''} placeholder="noreply@example.com"
onChange={(e) => updateConfig('from', e.target.value)} />
/> <ConfigInput
</Field> {...control}
<Field label="收件人 (多个用逗号分隔)"> required
<input label="收件人 (多个用逗号分隔)"
required name="to"
className="input-base" placeholder="user@example.com"
placeholder="user@example.com" />
value={config.to ?? ''}
onChange={(e) => updateConfig('to', e.target.value)}
/>
</Field>
</> </>
) )
} }