121 lines
3.9 KiB
PHP
121 lines
3.9 KiB
PHP
<?php
|
|
|
|
namespace App\Livewire\Admin;
|
|
|
|
use App\Models\ExportTarget;
|
|
use App\Services\Secrets\SecretVault;
|
|
use LivewireUI\Modal\ModalComponent;
|
|
|
|
/**
|
|
* Adding or changing one destination.
|
|
*
|
|
* The password never lives here. It goes into the secret vault under a key
|
|
* derived from the target, and only that key is stored on the row — a
|
|
* credential in a settings table is a credential in every database dump. An
|
|
* empty password field on an existing target means "leave it alone", not
|
|
* "clear it": the field cannot show what is stored, so treating blank as a
|
|
* deletion would wipe the credential every time somebody renamed the target.
|
|
*/
|
|
class EditExportTarget extends ModalComponent
|
|
{
|
|
public string $uuid = '';
|
|
|
|
public string $name = '';
|
|
|
|
public string $driver = ExportTarget::LOCAL;
|
|
|
|
public string $path = '';
|
|
|
|
public string $host = '';
|
|
|
|
public ?int $port = 22;
|
|
|
|
public string $username = '';
|
|
|
|
public string $password = '';
|
|
|
|
public bool $hasPassword = false;
|
|
|
|
public bool $active = true;
|
|
|
|
public function mount(?string $uuid = null): void
|
|
{
|
|
$this->authorize('site.manage'); // modals bypass the route middleware
|
|
|
|
if ($uuid === null) {
|
|
return;
|
|
}
|
|
|
|
$target = ExportTarget::query()->where('uuid', $uuid)->firstOrFail();
|
|
|
|
$this->uuid = $uuid;
|
|
$this->name = $target->name;
|
|
$this->driver = $target->driver;
|
|
$this->path = $target->path;
|
|
$this->host = (string) $target->host;
|
|
$this->port = $target->port ?: 22;
|
|
$this->username = (string) $target->username;
|
|
$this->active = (bool) $target->active;
|
|
$this->hasPassword = $target->secret_key !== null
|
|
&& app(SecretVault::class)->has($target->secret_key);
|
|
}
|
|
|
|
public function save()
|
|
{
|
|
$this->authorize('site.manage');
|
|
|
|
$rules = [
|
|
'name' => 'required|string|max:120',
|
|
'driver' => 'required|in:local,sftp',
|
|
'path' => 'required|string|max:255',
|
|
'active' => 'boolean',
|
|
];
|
|
|
|
if ($this->driver === ExportTarget::SFTP) {
|
|
$rules += [
|
|
'host' => 'required|string|max:190',
|
|
'port' => 'required|integer|min:1|max:65535',
|
|
'username' => 'required|string|max:120',
|
|
// Required only when there is nothing stored yet. Blank on an
|
|
// existing target means "unchanged" — the field cannot show
|
|
// what is stored, so treating blank as a deletion would wipe
|
|
// the credential on every unrelated edit.
|
|
'password' => $this->hasPassword ? 'nullable|string|max:255' : 'required|string|max:255',
|
|
];
|
|
}
|
|
|
|
$data = $this->validate($rules);
|
|
|
|
$target = $this->uuid !== ''
|
|
? ExportTarget::query()->where('uuid', $this->uuid)->firstOrFail()
|
|
: new ExportTarget;
|
|
|
|
$target->fill([
|
|
'name' => $data['name'],
|
|
'driver' => $data['driver'],
|
|
'path' => rtrim($data['path'], '/') ?: '/',
|
|
'host' => $this->driver === ExportTarget::SFTP ? $this->host : null,
|
|
'port' => $this->driver === ExportTarget::SFTP ? $this->port : null,
|
|
'username' => $this->driver === ExportTarget::SFTP ? $this->username : null,
|
|
'active' => $data['active'] ?? true,
|
|
]);
|
|
|
|
$target->save();
|
|
|
|
if ($this->driver === ExportTarget::SFTP && $this->password !== '') {
|
|
$key = 'export_target.'.$target->uuid;
|
|
app(SecretVault::class)->put($key, $this->password, auth('operator')->user());
|
|
$target->forceFill(['secret_key' => $key])->save();
|
|
}
|
|
|
|
$this->dispatch('notify', message: __('finance.targets_saved'));
|
|
|
|
return $this->redirectRoute('admin.finance', navigate: true);
|
|
}
|
|
|
|
public function render()
|
|
{
|
|
return view('livewire.admin.edit-export-target');
|
|
}
|
|
}
|