Skip to content

Commit 4a3dfa6

Browse files
[Domains] Edit DNS Provider (#1051)
* edit-dns-provider * fixes
1 parent 9ec070b commit 4a3dfa6

File tree

12 files changed

+287
-14
lines changed

12 files changed

+287
-14
lines changed

app/Actions/DNSProvider/EditDNSProvider.php

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,32 @@ class EditDNSProvider
1515
*/
1616
public function edit(DNSProvider $dnsProvider, array $input): DNSProvider
1717
{
18-
Validator::make($input, [
19-
'name' => [
20-
'required',
21-
],
22-
])->validate();
18+
$provider = $dnsProvider->provider();
19+
20+
$rules = array_merge(
21+
['name' => ['required']],
22+
$provider->editValidationRules($input),
23+
);
24+
25+
Validator::make($input, $rules)->validate();
2326

2427
$dnsProvider->name = $input['name'];
2528
$dnsProvider->project_id = isset($input['global']) && $input['global'] ? null : $dnsProvider->user->currentProject?->id;
29+
30+
[$newCredentials, $needsReconnect] = $provider->mergeEditData($input);
31+
32+
if ($needsReconnect) {
33+
if (! $provider->connect($newCredentials)) {
34+
throw ValidationException::withMessages([
35+
'provider' => [sprintf("Couldn't connect to %s. Please check your credentials.", $dnsProvider->provider)],
36+
]);
37+
}
38+
}
39+
40+
if ($newCredentials !== $dnsProvider->credentials) {
41+
$dnsProvider->credentials = $newCredentials;
42+
}
43+
2644
$dnsProvider->connected = true;
2745
$dnsProvider->save();
2846

app/DNSProviders/AbstractDNSProvider.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,32 @@ public function credentialData(array $input): array
3030
];
3131
}
3232

33+
/**
34+
* @return array<string, mixed>
35+
*/
36+
public function editableData(): array
37+
{
38+
return [];
39+
}
40+
41+
/**
42+
* @param array<string, mixed> $input
43+
* @return array{0: array<string, mixed>, 1: bool}
44+
*/
45+
public function mergeEditData(array $input): array
46+
{
47+
return [$this->dnsProvider->credentials, false];
48+
}
49+
50+
/**
51+
* @param array<string, mixed> $input
52+
* @return array<string, string|array<int, mixed>>
53+
*/
54+
public function editValidationRules(array $input): array
55+
{
56+
return [];
57+
}
58+
3359
/**
3460
* @return array<string, mixed>
3561
*/

app/DNSProviders/Cloudflare.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,26 @@ public function credentialData(array $input): array
4545
];
4646
}
4747

48+
public function editValidationRules(array $input): array
49+
{
50+
return [
51+
'token' => 'nullable|string',
52+
];
53+
}
54+
55+
public function mergeEditData(array $input): array
56+
{
57+
$credentials = $this->dnsProvider->credentials;
58+
$needsReconnect = false;
59+
60+
if (! empty($input['token'])) {
61+
$credentials['token'] = $input['token'];
62+
$needsReconnect = true;
63+
}
64+
65+
return [$credentials, $needsReconnect];
66+
}
67+
4868
public function connect(array $credentials): bool
4969
{
5070
try {

app/DNSProviders/DNSProvider.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,30 @@ public function validationRules(array $input): array;
1818
*/
1919
public function credentialData(array $input): array;
2020

21+
/**
22+
* Non-sensitive credential data that can be exposed for editing.
23+
*
24+
* @return array<string, mixed>
25+
*/
26+
public function editableData(): array;
27+
28+
/**
29+
* Merge edit input into existing credentials, ignoring empty optional fields.
30+
* Returns [credentials, needsReconnect] tuple.
31+
*
32+
* @param array<string, mixed> $input
33+
* @return array{0: array<string, mixed>, 1: bool}
34+
*/
35+
public function mergeEditData(array $input): array;
36+
37+
/**
38+
* Validation rules for edit form (fields are optional by default).
39+
*
40+
* @param array<string, mixed> $input
41+
* @return array<string, string|array<int, mixed>>
42+
*/
43+
public function editValidationRules(array $input): array;
44+
2145
/**
2246
* @param array<string, mixed> $credentials
2347
*/

app/Http/Resources/DNSProviderResource.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ public function toArray(Request $request): array
2323
'connected' => $this->connected,
2424
'project_id' => $this->project_id,
2525
'global' => is_null($this->project_id),
26+
'editable_data' => $this->provider()->editableData(),
2627
'created_at' => $this->created_at,
2728
'updated_at' => $this->updated_at,
2829
];

app/Plugins/RegisterDNSProvider.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ public function __construct(
1111
private string $label = '',
1212
private string $handler = '',
1313
private ?DynamicForm $form = null,
14+
private ?DynamicForm $editForm = null,
1415
private array $proxyTypes = [],
1516
private bool $supportsCreatedAt = true,
1617
) {}
@@ -48,6 +49,13 @@ public function form(DynamicForm $form): self
4849
return $this;
4950
}
5051

52+
public function editForm(DynamicForm $editForm): self
53+
{
54+
$this->editForm = $editForm;
55+
56+
return $this;
57+
}
58+
5159
public function proxyTypes(array $proxyTypes): self
5260
{
5361
$this->proxyTypes = $proxyTypes;
@@ -70,6 +78,7 @@ public function register(): void
7078
'label' => $this->label,
7179
'handler' => $this->handler,
7280
'form' => $this->form ? $this->form->toArray() : [],
81+
'edit_form' => $this->editForm ? $this->editForm->toArray() : [],
7382
'proxy_types' => $this->proxyTypes,
7483
'supports_created_at' => $this->supportsCreatedAt,
7584
];

app/Providers/DNSProviderServiceProvider.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,14 @@ private function cloudflare(): void
3030
->description('Create an API token with Zone:Read and DNS:Edit permissions'),
3131
])
3232
)
33+
->editForm(
34+
DynamicForm::make([
35+
DynamicField::make('token')
36+
->passwordWithToggle()
37+
->label('API Token')
38+
->description('Leave empty to keep the current token'),
39+
])
40+
)
3341
->proxyTypes(['A', 'AAAA', 'CNAME'])
3442
->supportsCreatedAt(true)
3543
->register();

resources/js/pages/dns-providers/components/columns.tsx

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
} from '@/components/ui/dialog';
1515
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from '@/components/ui/dropdown-menu';
1616
import { Button } from '@/components/ui/button';
17-
import { useForm } from '@inertiajs/react';
17+
import { useForm, usePage } from '@inertiajs/react';
1818
import { LoaderCircleIcon, MoreVerticalIcon } from 'lucide-react';
1919
import FormSuccessful from '@/components/form-successful';
2020
import { FormEvent, useState } from 'react';
@@ -23,24 +23,41 @@ import { Form, FormField, FormFields } from '@/components/ui/form';
2323
import { Label } from '@/components/ui/label';
2424
import { Input } from '@/components/ui/input';
2525
import { Checkbox } from '@/components/ui/checkbox';
26+
import { SharedData } from '@/types';
27+
import { DynamicFieldConfig } from '@/types/dynamic-field-config';
28+
import DynamicFieldComponent from '@/components/ui/dynamic-field';
2629

2730
function Edit({ dnsProvider }: { dnsProvider: DNSProvider }) {
2831
const [open, setOpen] = useState(false);
29-
const form = useForm({
32+
const page = usePage<SharedData>();
33+
const providerConfig = page.props.configs.dns_provider.providers[dnsProvider.provider];
34+
const editFields: DynamicFieldConfig[] = providerConfig?.edit_form || [];
35+
36+
const initialData: Record<string, string | number | boolean | string[]> = {
3037
name: dnsProvider.name,
3138
global: dnsProvider.project_id === null,
39+
};
40+
41+
// Pre-fill editable (non-sensitive) data, leave credential fields empty
42+
editFields.forEach((field) => {
43+
initialData[field.name] = dnsProvider.editable_data?.[field.name] ?? '';
3244
});
3345

46+
const form = useForm(initialData);
47+
3448
const submit = (e: FormEvent) => {
3549
e.preventDefault();
36-
form.patch(route('dns-providers.update', dnsProvider.id));
50+
form.patch(route('dns-providers.update', dnsProvider.id), {
51+
onSuccess: () => setOpen(false),
52+
});
3753
};
54+
3855
return (
3956
<Dialog open={open} onOpenChange={setOpen}>
4057
<DialogTrigger asChild>
4158
<DropdownMenuItem onSelect={(e) => e.preventDefault()}>Edit</DropdownMenuItem>
4259
</DialogTrigger>
43-
<DialogContent>
60+
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-xl">
4461
<DialogHeader>
4562
<DialogTitle>Edit {dnsProvider.name}</DialogTitle>
4663
<DialogDescription className="sr-only">Edit DNS provider</DialogDescription>
@@ -49,16 +66,26 @@ function Edit({ dnsProvider }: { dnsProvider: DNSProvider }) {
4966
<FormFields>
5067
<FormField>
5168
<Label htmlFor="name">Name</Label>
52-
<Input type="text" id="name" name="name" value={form.data.name} onChange={(e) => form.setData('name', e.target.value)} />
69+
<Input type="text" id="name" name="name" value={form.data.name as string} onChange={(e) => form.setData('name', e.target.value)} />
5370
<InputError message={form.errors.name} />
5471
</FormField>
72+
{editFields.map((field: DynamicFieldConfig) => (
73+
<DynamicFieldComponent
74+
key={`edit-field-${field.name}`}
75+
value={form.data[field.name]}
76+
onChange={(value) => form.setData(field.name, value)}
77+
config={field}
78+
error={form.errors[field.name]}
79+
/>
80+
))}
5581
<FormField>
5682
<div className="flex items-center space-x-3">
57-
<Checkbox id="global" name="global" checked={form.data.global} onClick={() => form.setData('global', !form.data.global)} />
83+
<Checkbox id="global" name="global" checked={form.data.global as boolean} onClick={() => form.setData('global', !form.data.global)} />
5884
<Label htmlFor="global">Is global (accessible in all projects)</Label>
5985
</div>
6086
<InputError message={form.errors.global} />
6187
</FormField>
88+
<InputError message={form.errors.provider} />
6289
</FormFields>
6390
</Form>
6491
<DialogFooter>

resources/js/types/dns-provider.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export interface DNSProvider {
55
connected: boolean;
66
project_id: number | null;
77
global: boolean;
8+
editable_data: Record<string, string>;
89
created_at: string;
910
updated_at: string;
1011
}

resources/js/types/index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ export interface Configs {
7575
label: string;
7676
handler: string;
7777
form?: DynamicFieldConfig[];
78+
edit_form?: DynamicFieldConfig[];
7879
proxy_types?: string[];
7980
supports_created_at?: boolean;
8081
};

0 commit comments

Comments
 (0)