feat: 修改前端显示
This commit is contained in:
@@ -13,6 +13,7 @@ import {
|
||||
mapServerFieldToFormField,
|
||||
taskFormSchema,
|
||||
taskToFormValues,
|
||||
type KeyValueFormValue,
|
||||
type TaskFormValues,
|
||||
} from '../lib/taskForm'
|
||||
import { useFocusTrap } from '../lib/useFocusTrap'
|
||||
@@ -53,6 +54,7 @@ export function TaskEditorModal({
|
||||
const actionType = watch('actionType')
|
||||
const cronValue = watch('cron')
|
||||
const timezoneValue = watch('timezone')
|
||||
const scriptPreview = watch('script')
|
||||
const debouncedCron = useDebouncedValue(cronValue, 300)
|
||||
const debouncedTimezone = useDebouncedValue(timezoneValue, 300)
|
||||
const cronPreview = useQuery({
|
||||
@@ -169,7 +171,12 @@ export function TaskEditorModal({
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<form className="modal-body" onSubmit={submit} noValidate>
|
||||
<form
|
||||
id="task-editor-form"
|
||||
className="modal-body"
|
||||
onSubmit={submit}
|
||||
noValidate
|
||||
>
|
||||
{submitError ? (
|
||||
<div className="inline-alert inline-alert-error" role="alert">
|
||||
{submitError}
|
||||
@@ -177,7 +184,10 @@ export function TaskEditorModal({
|
||||
) : null}
|
||||
|
||||
<div className="form-grid">
|
||||
<Field label="任务标识" error={errors.id?.message}>
|
||||
<Field
|
||||
label="任务标识 (Task ID)"
|
||||
error={errors.id?.message}
|
||||
>
|
||||
<input
|
||||
{...register('id')}
|
||||
className="field-input mono"
|
||||
@@ -187,7 +197,7 @@ export function TaskEditorModal({
|
||||
aria-invalid={Boolean(errors.id)}
|
||||
/>
|
||||
</Field>
|
||||
<Field label="任务名称" error={errors.name?.message}>
|
||||
<Field label="名称" error={errors.name?.message}>
|
||||
<input
|
||||
{...register('name')}
|
||||
className="field-input"
|
||||
@@ -251,7 +261,7 @@ export function TaskEditorModal({
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<Field label="超时时间(秒)" error={errors.timeout_secs?.message}>
|
||||
<Field label="超时时间 (秒)" error={errors.timeout_secs?.message}>
|
||||
<input
|
||||
{...register('timeout_secs', { valueAsNumber: true })}
|
||||
type="number"
|
||||
@@ -266,7 +276,7 @@ export function TaskEditorModal({
|
||||
</Field>
|
||||
</div>
|
||||
|
||||
<div className="form-grid">
|
||||
<div className="form-grid form-grid-single">
|
||||
<Field label="IANA 时区" error={errors.timezone?.message}>
|
||||
<input
|
||||
{...register('timezone')}
|
||||
@@ -283,26 +293,19 @@ export function TaskEditorModal({
|
||||
<option value="America/New_York" />
|
||||
</datalist>
|
||||
</Field>
|
||||
<Field label="任务类型">
|
||||
<Field label="任务类型 (Action Type)">
|
||||
<select {...register('actionType')} className="field-input">
|
||||
<option value="script">执行脚本</option>
|
||||
<option value="http">HTTP 请求</option>
|
||||
<option value="process">二进制进程</option>
|
||||
<option value="script">
|
||||
执行脚本 (Python / Shell / Node / Lua)
|
||||
</option>
|
||||
<option value="http">发起 HTTP 请求</option>
|
||||
<option value="process">管理常驻/独立二进制</option>
|
||||
</select>
|
||||
</Field>
|
||||
</div>
|
||||
|
||||
<label className="checkbox-field">
|
||||
<input type="checkbox" {...register('enabled')} />
|
||||
<span>
|
||||
<strong>立即激活此任务调度</strong>
|
||||
<small>保存后立即注册到 Cron 调度器</small>
|
||||
</span>
|
||||
</label>
|
||||
|
||||
{actionType === 'script' ? (
|
||||
<fieldset className="action-panel">
|
||||
<legend>脚本配置</legend>
|
||||
<div className="dynamic-fields">
|
||||
<div className="form-grid">
|
||||
<Field
|
||||
label="工作目录"
|
||||
@@ -344,7 +347,9 @@ export function TaskEditorModal({
|
||||
<button
|
||||
type="button"
|
||||
className="button button-secondary button-small"
|
||||
onClick={() => scriptArgs.append({ value: '' })}
|
||||
onClick={() =>
|
||||
scriptArgs.append({ key: '', value: '' })
|
||||
}
|
||||
>
|
||||
+ 添加参数
|
||||
</button>
|
||||
@@ -352,10 +357,15 @@ export function TaskEditorModal({
|
||||
<div className="collection-list">
|
||||
{scriptArgs.fields.map((field, index) => (
|
||||
<div className="argument-row" key={field.id}>
|
||||
<input
|
||||
{...register(`script.args.${index}.key`)}
|
||||
className="field-input mono"
|
||||
placeholder="参数名 (如 --mode 或 -v)"
|
||||
/>
|
||||
<input
|
||||
{...register(`script.args.${index}.value`)}
|
||||
className="field-input mono"
|
||||
placeholder="例如:--mode"
|
||||
placeholder="参数值 (可选,无值可留空)"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
@@ -385,12 +395,38 @@ export function TaskEditorModal({
|
||||
onAdd={() => scriptEnv.append({ key: '', value: '' })}
|
||||
onRemove={scriptEnv.remove}
|
||||
/>
|
||||
</fieldset>
|
||||
|
||||
<div className="cli-preview">
|
||||
<div className="cli-preview-header">
|
||||
<span>调用预览 (Runtime Preview)</span>
|
||||
<span>Windows 原生环境</span>
|
||||
</div>
|
||||
<div className="cli-preview-body">
|
||||
{scriptPreview.env
|
||||
.filter((item) => item.key.trim())
|
||||
.map((item, index) => (
|
||||
<div
|
||||
className="cli-line-env"
|
||||
key={`${item.key}-${index}`}
|
||||
>
|
||||
set {item.key.trim()}=
|
||||
{isSensitiveName(item.key)
|
||||
? '[REDACTED]'
|
||||
: item.value || '""'}
|
||||
</div>
|
||||
))}
|
||||
<div className="cli-line-cmd">
|
||||
{scriptPreview.interpreter.trim() || 'python.exe'}{' '}
|
||||
{scriptPreview.script.trim() || 'main.py'}
|
||||
{formatArgumentPreview(scriptPreview.args)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{actionType === 'http' ? (
|
||||
<fieldset className="action-panel">
|
||||
<legend>HTTP 配置</legend>
|
||||
<div className="dynamic-fields">
|
||||
<div className="form-grid">
|
||||
<Field label="请求 URL" error={errors.http?.url?.message}>
|
||||
<input
|
||||
@@ -436,12 +472,11 @@ export function TaskEditorModal({
|
||||
onAdd={() => httpHeaders.append({ key: '', value: '' })}
|
||||
onRemove={httpHeaders.remove}
|
||||
/>
|
||||
</fieldset>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{actionType === 'process' ? (
|
||||
<fieldset className="action-panel">
|
||||
<legend>进程配置</legend>
|
||||
<div className="dynamic-fields">
|
||||
<div className="form-grid">
|
||||
<Field
|
||||
label="可执行程序"
|
||||
@@ -469,7 +504,9 @@ export function TaskEditorModal({
|
||||
<button
|
||||
type="button"
|
||||
className="button button-secondary button-small"
|
||||
onClick={() => processArgs.append({ value: '' })}
|
||||
onClick={() =>
|
||||
processArgs.append({ key: '', value: '' })
|
||||
}
|
||||
>
|
||||
+ 添加参数
|
||||
</button>
|
||||
@@ -477,10 +514,15 @@ export function TaskEditorModal({
|
||||
<div className="collection-list">
|
||||
{processArgs.fields.map((field, index) => (
|
||||
<div className="argument-row" key={field.id}>
|
||||
<input
|
||||
{...register(`process.args.${index}.key`)}
|
||||
className="field-input mono"
|
||||
placeholder="参数名 (如 --port)"
|
||||
/>
|
||||
<input
|
||||
{...register(`process.args.${index}.value`)}
|
||||
className="field-input mono"
|
||||
placeholder="例如:--port"
|
||||
placeholder="参数值 (可选)"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
@@ -509,32 +551,68 @@ export function TaskEditorModal({
|
||||
onAdd={() => processEnv.append({ key: '', value: '' })}
|
||||
onRemove={processEnv.remove}
|
||||
/>
|
||||
</fieldset>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<footer className="form-actions">
|
||||
<button
|
||||
type="button"
|
||||
className="button button-secondary"
|
||||
onClick={onClose}
|
||||
disabled={saving}
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
className="button button-primary"
|
||||
disabled={saving}
|
||||
>
|
||||
{saving ? '保存中...' : task ? '保存修改' : '保存并热刷新'}
|
||||
</button>
|
||||
</footer>
|
||||
<label className="checkbox-field">
|
||||
<input type="checkbox" {...register('enabled')} />
|
||||
<span>
|
||||
<strong>立即激活此任务调度</strong>
|
||||
<small>保存后立即注册到 Cron 调度器</small>
|
||||
</span>
|
||||
</label>
|
||||
</form>
|
||||
|
||||
<footer className="modal-footer">
|
||||
<button
|
||||
type="button"
|
||||
className="button button-secondary"
|
||||
onClick={onClose}
|
||||
disabled={saving}
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
form="task-editor-form"
|
||||
className="button button-primary"
|
||||
disabled={saving}
|
||||
>
|
||||
{saving ? '保存中...' : task ? '保存修改' : '保存并热刷新'}
|
||||
</button>
|
||||
</footer>
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function formatArgumentPreview(argumentsList: KeyValueFormValue[]): string {
|
||||
const values = argumentsList.flatMap(({ key, value }) => {
|
||||
const argument = key.trim()
|
||||
const argumentValue = value.trim()
|
||||
return [
|
||||
argument,
|
||||
argumentValue.includes(' ') ? `"${argumentValue}"` : argumentValue,
|
||||
].filter(Boolean)
|
||||
})
|
||||
return values.length ? ` ${values.join(' ')}` : ''
|
||||
}
|
||||
|
||||
function isSensitiveName(name: string): boolean {
|
||||
const normalized = name.toUpperCase()
|
||||
return [
|
||||
'SECRET',
|
||||
'TOKEN',
|
||||
'PASSWORD',
|
||||
'PASS',
|
||||
'API_KEY',
|
||||
'PRIVATE_KEY',
|
||||
'CREDENTIAL',
|
||||
'AUTHORIZATION',
|
||||
'AUTH',
|
||||
].some((marker) => normalized.includes(marker))
|
||||
}
|
||||
|
||||
interface FieldProps {
|
||||
label: string
|
||||
error?: string
|
||||
|
||||
Reference in New Issue
Block a user