Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8204105fd6 |
@@ -46,8 +46,13 @@
|
||||
let csvExporting = $state(false);
|
||||
let csvFileInput = $state<HTMLInputElement | null>(null);
|
||||
|
||||
const selectedCount = $derived(selectedIds.size);
|
||||
const allSelected = $derived(entries.length > 0 && selectedIds.size === entries.length);
|
||||
const activeSelected = $derived.by(() => {
|
||||
const allowed = new Set(entries.map((e) => e.id));
|
||||
return [...selectedIds].filter((id) => allowed.has(id));
|
||||
});
|
||||
const selectedCount = $derived(activeSelected.length);
|
||||
|
||||
const allSelected = $derived(entries.length > 0 && entries.every((e) => selectedIds.has(e.id)));
|
||||
|
||||
const columns = [
|
||||
{ id: 'select', label: '', class: 'w-10' },
|
||||
@@ -75,11 +80,6 @@
|
||||
{ id: 'actions', label: '', class: 'w-20' }
|
||||
] as const;
|
||||
|
||||
$effect(() => {
|
||||
const validIds = new Set(entries.map((e) => e.id));
|
||||
selectedIds = new Set([...selectedIds].filter((id) => validIds.has(id)));
|
||||
});
|
||||
|
||||
function toggleSelection(id: string) {
|
||||
const next = new Set(selectedIds);
|
||||
if (next.has(id)) next.delete(id);
|
||||
@@ -133,7 +133,7 @@
|
||||
deletingBulk = true;
|
||||
let deleted = 0;
|
||||
try {
|
||||
for (const id of selectedIds) {
|
||||
for (const id of activeSelected) {
|
||||
try {
|
||||
await apiMutate(`/v1/modules/${moduleId}/as-entries/${id}`, 'DELETE', undefined, {
|
||||
idempotent: false
|
||||
|
||||
@@ -35,12 +35,23 @@
|
||||
|
||||
let saving = $state(false);
|
||||
let form = $state<AsEntryCreate>({ asn: 0, community_id: null });
|
||||
let initKey = $state('');
|
||||
|
||||
function resetForm() {
|
||||
form = edit
|
||||
? { asn: edit.asn, community_id: edit.community_id }
|
||||
: { asn: 0, community_id: null };
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
if (open) {
|
||||
form = edit
|
||||
? { asn: edit.asn, community_id: edit.community_id }
|
||||
: { asn: 0, community_id: null };
|
||||
if (!open) {
|
||||
initKey = '';
|
||||
return;
|
||||
}
|
||||
const nextKey = edit?.id ?? 'new';
|
||||
if (nextKey !== initKey) {
|
||||
initKey = nextKey;
|
||||
resetForm();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -75,7 +86,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<Dialog {open} onOpenChange={handleOpenChange}>
|
||||
<Dialog bind:open onOpenChange={handleOpenChange}>
|
||||
<DialogContent class="sm:max-w-sm">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{edit ? 'Редактировать запись' : 'Новая AS-запись'}</DialogTitle>
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
prefix_path: '',
|
||||
community_id: null
|
||||
});
|
||||
let initKey = $state('');
|
||||
|
||||
function clearPreview() {
|
||||
previewLoading = false;
|
||||
@@ -61,18 +62,28 @@
|
||||
previewOk = false;
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
clearPreview();
|
||||
form = edit
|
||||
? {
|
||||
url: edit.url,
|
||||
source_kind: normalizeCdnSourceKind(edit.source_kind),
|
||||
prefix_path: edit.prefix_path ?? '',
|
||||
community_id: edit.community_id,
|
||||
refresh_interval_sec: edit.refresh_interval_sec
|
||||
}
|
||||
: { url: '', source_kind: 'plaintext', prefix_path: '', community_id: null };
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
if (open) {
|
||||
clearPreview();
|
||||
form = edit
|
||||
? {
|
||||
url: edit.url,
|
||||
source_kind: normalizeCdnSourceKind(edit.source_kind),
|
||||
prefix_path: edit.prefix_path ?? '',
|
||||
community_id: edit.community_id,
|
||||
refresh_interval_sec: edit.refresh_interval_sec
|
||||
}
|
||||
: { url: '', source_kind: 'plaintext', prefix_path: '', community_id: null };
|
||||
if (!open) {
|
||||
initKey = '';
|
||||
return;
|
||||
}
|
||||
const nextKey = edit?.id ?? 'new';
|
||||
if (nextKey !== initKey) {
|
||||
initKey = nextKey;
|
||||
resetForm();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -150,7 +161,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<Dialog {open} onOpenChange={handleOpenChange}>
|
||||
<Dialog bind:open onOpenChange={handleOpenChange}>
|
||||
<DialogContent class="sm:max-w-lg">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{edit ? 'Редактировать источник' : 'Новый CDN-источник'}</DialogTitle>
|
||||
|
||||
@@ -39,8 +39,12 @@
|
||||
let selectedIds = $state(new Set<string>());
|
||||
let deletingBulk = $state(false);
|
||||
|
||||
const selectedCount = $derived(selectedIds.size);
|
||||
const allSelected = $derived(sources.length > 0 && selectedIds.size === sources.length);
|
||||
const activeSelected = $derived.by(() => {
|
||||
const allowed = new Set(sources.map((s) => s.id));
|
||||
return [...selectedIds].filter((id) => allowed.has(id));
|
||||
});
|
||||
const selectedCount = $derived(activeSelected.length);
|
||||
const allSelected = $derived(sources.length > 0 && sources.every((s) => selectedIds.has(s.id)));
|
||||
|
||||
const columns = [
|
||||
{ id: 'select', label: '', class: 'w-10' },
|
||||
@@ -62,11 +66,6 @@
|
||||
{ id: 'actions', label: '', class: 'w-20' }
|
||||
] as const;
|
||||
|
||||
$effect(() => {
|
||||
const validIds = new Set(sources.map((s) => s.id));
|
||||
selectedIds = new Set([...selectedIds].filter((id) => validIds.has(id)));
|
||||
});
|
||||
|
||||
function toggleSelection(id: string) {
|
||||
const next = new Set(selectedIds);
|
||||
if (next.has(id)) next.delete(id);
|
||||
@@ -120,7 +119,7 @@
|
||||
deletingBulk = true;
|
||||
let deleted = 0;
|
||||
try {
|
||||
for (const id of selectedIds) {
|
||||
for (const id of activeSelected) {
|
||||
try {
|
||||
await apiMutate(`/v1/modules/${moduleId}/cdn-sources/${id}`, 'DELETE', undefined, {
|
||||
idempotent: false
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<Dialog {open} onOpenChange={handleOpenChange}>
|
||||
<Dialog bind:open onOpenChange={handleOpenChange}>
|
||||
<DialogContent class="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Новый модуль</DialogTitle>
|
||||
|
||||
@@ -39,15 +39,15 @@
|
||||
<Badge variant={moduleEnabledBadgeVariant(!!mod.enabled)} class="text-xs">
|
||||
{moduleEnabledRu(!!mod.enabled)}
|
||||
</Badge>
|
||||
<Button variant="outline" size="sm" onclick={onRefresh} disabled={refreshing}>
|
||||
<Button variant="outline" size="sm" onclick={() => onRefresh()} disabled={refreshing}>
|
||||
<RefreshCw class={refreshing ? 'animate-spin' : ''} />
|
||||
Обновить
|
||||
</Button>
|
||||
<Button variant="outline" size="sm" onclick={onEdit}>
|
||||
<Button variant="outline" size="sm" onclick={() => onEdit()}>
|
||||
<Pencil />
|
||||
Редактировать
|
||||
</Button>
|
||||
<Button variant="destructive" size="sm" onclick={onDelete}>
|
||||
<Button variant="destructive" size="sm" onclick={() => onDelete()}>
|
||||
<Trash2 />
|
||||
Удалить
|
||||
</Button>
|
||||
|
||||
@@ -44,8 +44,12 @@
|
||||
let csvExporting = $state(false);
|
||||
let csvFileInput = $state<HTMLInputElement | null>(null);
|
||||
|
||||
const selectedCount = $derived(selectedIds.size);
|
||||
const allSelected = $derived(entries.length > 0 && selectedIds.size === entries.length);
|
||||
const activeSelected = $derived.by(() => {
|
||||
const allowed = new Set(entries.map((e) => e.id));
|
||||
return [...selectedIds].filter((id) => allowed.has(id));
|
||||
});
|
||||
const selectedCount = $derived(activeSelected.length);
|
||||
const allSelected = $derived(entries.length > 0 && entries.every((e) => selectedIds.has(e.id)));
|
||||
|
||||
const columns = [
|
||||
{ id: 'select', label: '', class: 'w-10' },
|
||||
@@ -54,11 +58,6 @@
|
||||
{ id: 'actions', label: '', class: 'w-20' }
|
||||
] as const;
|
||||
|
||||
$effect(() => {
|
||||
const validIds = new Set(entries.map((e) => e.id));
|
||||
selectedIds = new Set([...selectedIds].filter((id) => validIds.has(id)));
|
||||
});
|
||||
|
||||
function toggleSelection(id: string) {
|
||||
const next = new Set(selectedIds);
|
||||
if (next.has(id)) next.delete(id);
|
||||
@@ -112,7 +111,7 @@
|
||||
deletingBulk = true;
|
||||
let deleted = 0;
|
||||
try {
|
||||
for (const id of selectedIds) {
|
||||
for (const id of activeSelected) {
|
||||
try {
|
||||
await apiMutate(`/v1/modules/${moduleId}/domain-entries/${id}`, 'DELETE', undefined, {
|
||||
idempotent: false
|
||||
|
||||
@@ -34,12 +34,23 @@
|
||||
|
||||
let saving = $state(false);
|
||||
let form = $state<DomainEntryCreate>({ fqdn: '', community_id: null });
|
||||
let initKey = $state('');
|
||||
|
||||
function resetForm() {
|
||||
form = edit
|
||||
? { fqdn: edit.fqdn, community_id: edit.community_id }
|
||||
: { fqdn: '', community_id: null };
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
if (open) {
|
||||
form = edit
|
||||
? { fqdn: edit.fqdn, community_id: edit.community_id }
|
||||
: { fqdn: '', community_id: null };
|
||||
if (!open) {
|
||||
initKey = '';
|
||||
return;
|
||||
}
|
||||
const nextKey = edit?.id ?? 'new';
|
||||
if (nextKey !== initKey) {
|
||||
initKey = nextKey;
|
||||
resetForm();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -68,7 +79,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<Dialog {open} onOpenChange={handleOpenChange}>
|
||||
<Dialog bind:open onOpenChange={handleOpenChange}>
|
||||
<DialogContent class="sm:max-w-sm">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{edit ? 'Редактировать домен' : 'Новый домен'}</DialogTitle>
|
||||
|
||||
@@ -71,19 +71,30 @@
|
||||
|
||||
let editForm = $state<ModulePatch>({});
|
||||
let editSaving = $state(false);
|
||||
let initKey = $state('');
|
||||
|
||||
function resetEditForm() {
|
||||
editForm = {
|
||||
name: mod.name,
|
||||
enabled: mod.enabled,
|
||||
priority: mod.priority,
|
||||
refresh_interval_sec: mod.refresh_interval_sec,
|
||||
cron_expr: mod.cron_expr,
|
||||
default_community_id: mod.default_community_id,
|
||||
doh_profile_ids: moduleDohProfileIds(mod),
|
||||
doh_resolver_policy: mod.doh_resolver_policy ?? 'primary_only'
|
||||
};
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
if (open && mod) {
|
||||
editForm = {
|
||||
name: mod.name,
|
||||
enabled: mod.enabled,
|
||||
priority: mod.priority,
|
||||
refresh_interval_sec: mod.refresh_interval_sec,
|
||||
cron_expr: mod.cron_expr,
|
||||
default_community_id: mod.default_community_id,
|
||||
doh_profile_ids: moduleDohProfileIds(mod),
|
||||
doh_resolver_policy: mod.doh_resolver_policy ?? 'primary_only'
|
||||
};
|
||||
if (!open) {
|
||||
initKey = '';
|
||||
return;
|
||||
}
|
||||
const nextKey = mod.id;
|
||||
if (nextKey !== initKey) {
|
||||
initKey = nextKey;
|
||||
resetEditForm();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -136,7 +147,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<Dialog {open} onOpenChange={handleOpenChange}>
|
||||
<Dialog bind:open onOpenChange={handleOpenChange}>
|
||||
<DialogContent class="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Редактировать модуль</DialogTitle>
|
||||
|
||||
@@ -28,12 +28,23 @@
|
||||
|
||||
let saving = $state(false);
|
||||
let form = $state<IpRangeEntryCreate>({ prefix: '', community_id: '' });
|
||||
let initKey = $state('');
|
||||
|
||||
function resetForm() {
|
||||
form = edit
|
||||
? { prefix: edit.prefix, community_id: edit.community_id }
|
||||
: { prefix: '', community_id: '' };
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
if (open) {
|
||||
form = edit
|
||||
? { prefix: edit.prefix, community_id: edit.community_id }
|
||||
: { prefix: '', community_id: '' };
|
||||
if (!open) {
|
||||
initKey = '';
|
||||
return;
|
||||
}
|
||||
const nextKey = edit?.id ?? 'new';
|
||||
if (nextKey !== initKey) {
|
||||
initKey = nextKey;
|
||||
resetForm();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -62,7 +73,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<Dialog {open} onOpenChange={handleOpenChange}>
|
||||
<Dialog bind:open onOpenChange={handleOpenChange}>
|
||||
<DialogContent class="sm:max-w-sm">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{edit ? 'Редактировать диапазон' : 'Новый IP-диапазон'}</DialogTitle>
|
||||
|
||||
@@ -44,8 +44,12 @@
|
||||
let csvExporting = $state(false);
|
||||
let csvFileInput = $state<HTMLInputElement | null>(null);
|
||||
|
||||
const selectedCount = $derived(selectedIds.size);
|
||||
const allSelected = $derived(entries.length > 0 && selectedIds.size === entries.length);
|
||||
const activeSelected = $derived.by(() => {
|
||||
const allowed = new Set(entries.map((e) => e.id));
|
||||
return [...selectedIds].filter((id) => allowed.has(id));
|
||||
});
|
||||
const selectedCount = $derived(activeSelected.length);
|
||||
const allSelected = $derived(entries.length > 0 && entries.every((e) => selectedIds.has(e.id)));
|
||||
|
||||
const columns = [
|
||||
{ id: 'select', label: '', class: 'w-10' },
|
||||
@@ -59,11 +63,6 @@
|
||||
{ id: 'actions', label: '', class: 'w-20' }
|
||||
] as const;
|
||||
|
||||
$effect(() => {
|
||||
const validIds = new Set(entries.map((e) => e.id));
|
||||
selectedIds = new Set([...selectedIds].filter((id) => validIds.has(id)));
|
||||
});
|
||||
|
||||
function toggleSelection(id: string) {
|
||||
const next = new Set(selectedIds);
|
||||
if (next.has(id)) next.delete(id);
|
||||
@@ -120,7 +119,7 @@
|
||||
deletingBulk = true;
|
||||
let deleted = 0;
|
||||
try {
|
||||
for (const id of selectedIds) {
|
||||
for (const id of activeSelected) {
|
||||
try {
|
||||
await apiMutate(`/v1/modules/${moduleId}/ip-range-entries/${id}`, 'DELETE', undefined, {
|
||||
idempotent: false
|
||||
|
||||
@@ -15,7 +15,12 @@
|
||||
const open = $derived(!!state?.open);
|
||||
|
||||
function onOpenChange(v: boolean) {
|
||||
if (!v) closeConfirm();
|
||||
if (!v && state && !state.loading) closeConfirm();
|
||||
}
|
||||
|
||||
async function handleConfirm() {
|
||||
if (!state || state.loading) return;
|
||||
await state.onConfirm();
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -37,7 +42,10 @@
|
||||
? 'text-destructive-foreground bg-destructive hover:bg-destructive/90'
|
||||
: ''}
|
||||
disabled={state.loading}
|
||||
onclick={state.onConfirm}
|
||||
onclick={(e) => {
|
||||
e.preventDefault();
|
||||
void handleConfirm();
|
||||
}}
|
||||
>
|
||||
{state.loading ? '…' : (state.confirmLabel ?? 'Подтвердить')}
|
||||
</AlertDialogAction>
|
||||
|
||||
@@ -21,8 +21,9 @@ export function confirm(options: ConfirmOptions) {
|
||||
open: true,
|
||||
loading: false,
|
||||
onConfirm: async () => {
|
||||
if (!confirmState.current) return;
|
||||
confirmState.current = { ...confirmState.current, loading: true };
|
||||
const current = confirmState.current;
|
||||
if (!current || current.loading) return;
|
||||
confirmState.current = { ...current, loading: true };
|
||||
try {
|
||||
await options.onConfirm();
|
||||
resolve(true);
|
||||
|
||||
Reference in New Issue
Block a user