mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-12 18:26:36 +08:00
refactor: remove unused remarks and add quick domain create
Drop remark fields from Zone, Zone domains, proxy routes, WAF rule groups and IP groups across models, APIs, UI and DB columns (keep certificate/origin remarks). Add quick-create domain input for short labels, @ apex and full FQDNs when binding domains.
This commit is contained in:
@@ -338,7 +338,6 @@ export function buildPayloadFromRoute(
|
||||
cache_policy: route.cache_policy || 'url',
|
||||
cache_rules: route.cache_rule_list ?? [],
|
||||
custom_headers: route.custom_header_list ?? [],
|
||||
remark: route.remark || '',
|
||||
basic_auth_enabled: route.basic_auth_enabled,
|
||||
basic_auth_username: route.basic_auth_username,
|
||||
basic_auth_password: route.basic_auth_password,
|
||||
|
||||
@@ -11,7 +11,6 @@ import {Form, FormControl, FormDescription, FormField, FormItem, FormLabel, Form
|
||||
import {Input} from '@/components/ui/input';
|
||||
import {Sheet, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle,} from '@/components/ui/sheet';
|
||||
import {Switch} from '@/components/ui/switch';
|
||||
import {Textarea} from '@/components/ui/textarea';
|
||||
import type {ProxyRouteItem} from '@/lib/services/openflare';
|
||||
import {ProxyRouteService, ZoneService, zoneQueryKey} from '@/lib/services/openflare';
|
||||
|
||||
@@ -24,7 +23,6 @@ const createProxyRouteSchema = z
|
||||
zone_domain_ids: z.array(z.number().int().positive()).min(1, '请至少选择一个域名'),
|
||||
origin_url: z.string().trim().min(1, '请输入上游地址'),
|
||||
enabled: z.boolean(),
|
||||
remark: z.string().max(255, '备注不能超过 255 个字符'),
|
||||
})
|
||||
.superRefine((value, context) => {
|
||||
try {
|
||||
@@ -52,7 +50,6 @@ const defaultValues: CreateProxyRouteFormValues = {
|
||||
zone_domain_ids: [],
|
||||
origin_url: '',
|
||||
enabled: true,
|
||||
remark: '',
|
||||
};
|
||||
|
||||
interface ProxyRouteCreateSheetProps {
|
||||
@@ -120,7 +117,6 @@ export function ProxyRouteCreateSheet({
|
||||
cache_rules: [],
|
||||
custom_headers: [],
|
||||
basic_auth_enabled: false,
|
||||
remark: values.remark.trim(),
|
||||
upstream_type: 'direct',
|
||||
});
|
||||
|
||||
@@ -174,10 +170,13 @@ export function ProxyRouteCreateSheet({
|
||||
domains={domainsQuery.data ?? []}
|
||||
zones={zonesQuery.data ?? []}
|
||||
disabled={domainsQuery.isLoading}
|
||||
onDomainCreated={async () => {
|
||||
await domainsQuery.refetch();
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
域名与证书在 Zone 中维护;此处只选择本站点要绑定的 FQDN。
|
||||
勾选已登记域名,或使用「快捷新增域名」创建后自动勾选。
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -214,20 +213,6 @@ export function ProxyRouteCreateSheet({
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="remark"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>备注</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea placeholder="可选备注" rows={3} {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{form.formState.errors.root ? (
|
||||
<p className="text-sm text-destructive">{form.formState.errors.root.message}</p>
|
||||
) : null}
|
||||
|
||||
@@ -2,14 +2,17 @@
|
||||
|
||||
import {useMemo, useState} from 'react';
|
||||
import Link from 'next/link';
|
||||
import {ExternalLink} from 'lucide-react';
|
||||
import {ExternalLink, Plus} from 'lucide-react';
|
||||
|
||||
import {Badge} from '@/components/ui/badge';
|
||||
import {Button} from '@/components/ui/button';
|
||||
import {Checkbox} from '@/components/ui/checkbox';
|
||||
import {Input} from '@/components/ui/input';
|
||||
import {cn} from '@/lib/utils';
|
||||
import type {ZoneDomainItem, ZoneItem} from '@/lib/services/openflare';
|
||||
|
||||
import {QuickCreateZoneDomainDialog} from '../../websites/components/quick-create-zone-domain-dialog';
|
||||
|
||||
export interface ZoneDomainSelectorProps {
|
||||
value: number[];
|
||||
onChange: (ids: number[]) => void;
|
||||
@@ -19,6 +22,8 @@ export interface ZoneDomainSelectorProps {
|
||||
currentRouteId?: number | null;
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
/** Called after a domain is created so parent can refresh the catalog. */
|
||||
onDomainCreated?: (domain: ZoneDomainItem) => void | Promise<void>;
|
||||
}
|
||||
|
||||
export function ZoneDomainSelector({
|
||||
@@ -29,17 +34,31 @@ export function ZoneDomainSelector({
|
||||
currentRouteId = null,
|
||||
disabled = false,
|
||||
className,
|
||||
onDomainCreated,
|
||||
}: ZoneDomainSelectorProps) {
|
||||
const [keyword, setKeyword] = useState('');
|
||||
const [createOpen, setCreateOpen] = useState(false);
|
||||
const selected = useMemo(() => new Set(value), [value]);
|
||||
const zoneMap = useMemo(
|
||||
() => new Map(zones.map((zone) => [zone.id, zone.domain])),
|
||||
[zones],
|
||||
);
|
||||
|
||||
/** Hide domains already bound to another route; keep unbound + current route. */
|
||||
const availableDomains = useMemo(
|
||||
() =>
|
||||
domains.filter(
|
||||
(domain) =>
|
||||
domain.proxy_route_id == null || domain.proxy_route_id === currentRouteId,
|
||||
),
|
||||
[currentRouteId, domains],
|
||||
);
|
||||
|
||||
const filtered = useMemo(() => {
|
||||
const normalized = keyword.trim().toLowerCase();
|
||||
const list = [...domains].sort((a, b) => a.domain.localeCompare(b.domain));
|
||||
const list = [...availableDomains].sort((a, b) =>
|
||||
a.domain.localeCompare(b.domain),
|
||||
);
|
||||
if (!normalized) {
|
||||
return list;
|
||||
}
|
||||
@@ -51,16 +70,16 @@ export function ZoneDomainSelector({
|
||||
String(domain.id).includes(normalized)
|
||||
);
|
||||
});
|
||||
}, [domains, keyword, zoneMap]);
|
||||
}, [availableDomains, keyword, zoneMap]);
|
||||
|
||||
const toggle = (domain: ZoneDomainItem) => {
|
||||
if (disabled) {
|
||||
return;
|
||||
}
|
||||
const boundElsewhere =
|
||||
if (
|
||||
domain.proxy_route_id != null &&
|
||||
domain.proxy_route_id !== currentRouteId;
|
||||
if (boundElsewhere) {
|
||||
domain.proxy_route_id !== currentRouteId
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -73,12 +92,26 @@ export function ZoneDomainSelector({
|
||||
|
||||
return (
|
||||
<div className={cn('space-y-3', className)}>
|
||||
<Input
|
||||
value={keyword}
|
||||
onChange={(event) => setKeyword(event.target.value)}
|
||||
placeholder="搜索域名或 Zone…"
|
||||
disabled={disabled}
|
||||
/>
|
||||
<div className="flex flex-col gap-2 sm:flex-row sm:items-center">
|
||||
<Input
|
||||
value={keyword}
|
||||
onChange={(event) => setKeyword(event.target.value)}
|
||||
placeholder="搜索域名或 Zone…"
|
||||
disabled={disabled}
|
||||
className="sm:flex-1"
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
className="h-8 shrink-0 text-xs"
|
||||
disabled={disabled}
|
||||
onClick={() => setCreateOpen(true)}
|
||||
>
|
||||
<Plus className="mr-1 size-3.5" />
|
||||
快捷新增域名
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{filtered.length === 0 ? (
|
||||
<div className="rounded-lg border border-dashed px-4 py-8 text-center text-sm text-muted-foreground">
|
||||
@@ -88,8 +121,10 @@ export function ZoneDomainSelector({
|
||||
<Link href="/websites" className="text-primary underline-offset-4 hover:underline">
|
||||
网站管理
|
||||
</Link>{' '}
|
||||
中添加 FQDN。
|
||||
中添加 FQDN,或使用「快捷新增域名」。
|
||||
</>
|
||||
) : availableDomains.length === 0 ? (
|
||||
'没有可绑定的域名(其余域名已绑定其他路由)。可用「快捷新增域名」创建。'
|
||||
) : (
|
||||
'没有匹配的域名'
|
||||
)}
|
||||
@@ -97,9 +132,6 @@ export function ZoneDomainSelector({
|
||||
) : (
|
||||
<div className="max-h-72 space-y-1 overflow-y-auto rounded-lg border p-2">
|
||||
{filtered.map((domain) => {
|
||||
const boundElsewhere =
|
||||
domain.proxy_route_id != null &&
|
||||
domain.proxy_route_id !== currentRouteId;
|
||||
const checked = selected.has(domain.id);
|
||||
const zoneRoot = zoneMap.get(domain.zone_id);
|
||||
|
||||
@@ -108,13 +140,12 @@ export function ZoneDomainSelector({
|
||||
key={domain.id}
|
||||
className={cn(
|
||||
'flex cursor-pointer items-start gap-3 rounded-md px-2 py-2 transition-colors hover:bg-muted/60',
|
||||
boundElsewhere && 'cursor-not-allowed opacity-60',
|
||||
checked && !boundElsewhere && 'bg-muted/40',
|
||||
checked && 'bg-muted/40',
|
||||
)}
|
||||
>
|
||||
<Checkbox
|
||||
checked={checked}
|
||||
disabled={disabled || boundElsewhere}
|
||||
disabled={disabled}
|
||||
onCheckedChange={() => toggle(domain)}
|
||||
className="mt-0.5"
|
||||
/>
|
||||
@@ -130,11 +161,6 @@ export function ZoneDomainSelector({
|
||||
无证书
|
||||
</Badge>
|
||||
)}
|
||||
{boundElsewhere ? (
|
||||
<Badge variant="outline" className="text-[10px]">
|
||||
路由 #{domain.proxy_route_id}
|
||||
</Badge>
|
||||
) : null}
|
||||
</span>
|
||||
<span className="flex items-center gap-1 text-xs text-muted-foreground">
|
||||
{zoneRoot ? (
|
||||
@@ -156,6 +182,19 @@ export function ZoneDomainSelector({
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<QuickCreateZoneDomainDialog
|
||||
open={createOpen}
|
||||
onOpenChange={setCreateOpen}
|
||||
zones={zones}
|
||||
onCreated={async (domain) => {
|
||||
// Auto-select the new domain for this route binding.
|
||||
if (!value.includes(domain.id)) {
|
||||
onChange([...value, domain.id].sort((a, b) => a - b));
|
||||
}
|
||||
await onDomainCreated?.(domain);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user