Add "Delete files" task (#470)

* started "delete files" task

* add logic to DeleteFilesService

* add frontend

* make nicer

* move description to right place
This commit is contained in:
Boy132 2024-07-10 09:25:15 +02:00 committed by GitHub
parent 447e889a4f
commit bb7c0e0e66
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 68 additions and 4 deletions

View File

@ -19,7 +19,7 @@ class StoreTaskRequest extends ViewScheduleRequest
public function rules(): array
{
return [
'action' => 'required|in:command,power,backup',
'action' => 'required|in:command,power,backup,delete_files',
'payload' => 'required_unless:action,backup|string|nullable',
'time_offset' => 'required|numeric|min:0|max:900',
'sequence_id' => 'sometimes|required|numeric|min:1',

View File

@ -12,6 +12,7 @@ use Illuminate\Foundation\Bus\DispatchesJobs;
use App\Services\Backups\InitiateBackupService;
use App\Repositories\Daemon\DaemonPowerRepository;
use App\Exceptions\Http\Connection\DaemonConnectionException;
use App\Services\Files\DeleteFilesService;
class RunTaskJob extends Job implements ShouldQueue
{
@ -34,7 +35,8 @@ class RunTaskJob extends Job implements ShouldQueue
*/
public function handle(
InitiateBackupService $backupService,
DaemonPowerRepository $powerRepository
DaemonPowerRepository $powerRepository,
DeleteFilesService $deleteFilesService
): void {
// Do not process a task that is not set to active, unless it's been manually triggered.
if (!$this->task->schedule->is_active && !$this->manualRun) {
@ -67,6 +69,9 @@ class RunTaskJob extends Job implements ShouldQueue
case Task::ACTION_BACKUP:
$backupService->setIgnoredFiles(explode(PHP_EOL, $this->task->payload))->handle($server, null, true);
break;
case Task::ACTION_DELETE_FILES:
$deleteFilesService->handle($server, explode(PHP_EOL, $this->task->payload));
break;
default:
throw new \InvalidArgumentException('Invalid task action provided: ' . $this->task->action);
}

View File

@ -33,6 +33,7 @@ class Task extends Model
public const ACTION_POWER = 'power';
public const ACTION_COMMAND = 'command';
public const ACTION_BACKUP = 'backup';
public const ACTION_DELETE_FILES = 'delete_files';
/**
* The table associated with the model.

View File

@ -0,0 +1,41 @@
<?php
namespace App\Services\Files;
use App\Exceptions\Http\Connection\DaemonConnectionException;
use App\Models\Server;
use App\Repositories\Daemon\DaemonFileRepository;
use Illuminate\Support\Str;
class DeleteFilesService
{
/**
* DeleteFilesService constructor.
*/
public function __construct(
private DaemonFileRepository $daemonFileRepository
) {
}
/**
* Deletes the given files.
* @throws DaemonConnectionException
*/
public function handle(Server $server, array $files): void
{
$filesToDelete = collect();
foreach ($files as $line) {
$path = dirname($line);
$pattern = basename($line);
collect($this->daemonFileRepository->setServer($server)->getDirectory($path))->each(function ($item) use ($path, $pattern, $filesToDelete) {
if (Str::is($pattern, $item['name'])) {
$filesToDelete->push($path . '/' . $item['name']);
}
});
}
if ($filesToDelete->isNotEmpty()) {
$this->daemonFileRepository->setServer($server)->deleteFiles('/', $filesToDelete->toArray());
}
}
}

View File

@ -9,6 +9,7 @@ import {
faPencilAlt,
faToggleOn,
faTrashAlt,
faTrash,
} from '@fortawesome/free-solid-svg-icons';
import deleteScheduleTask from '@/api/server/schedules/deleteScheduleTask';
import { httpErrorToHuman } from '@/api/http';
@ -35,6 +36,8 @@ const getActionDetails = (action: string): [string, any] => {
return ['Send Power Action', faToggleOn];
case 'backup':
return ['Create Backup', faFileArchive];
case 'delete_files':
return ['Delete Files', faTrash];
default:
return ['Unknown Action', faCode];
}
@ -94,6 +97,9 @@ export default ({ schedule, task }: Props) => {
{task.action === 'backup' && (
<p css={tw`text-xs uppercase text-neutral-400 mb-1`}>Ignoring files & folders:</p>
)}
{task.action === 'delete_files' && (
<p css={tw`text-xs uppercase text-neutral-400 mb-1`}>Files to delete:</p>
)}
<div
css={tw`font-mono bg-neutral-800 rounded py-1 px-2 text-sm w-auto inline-block whitespace-pre-wrap break-all`}
>

View File

@ -34,7 +34,7 @@ interface Values {
}
const schema = object().shape({
action: string().required().oneOf(['command', 'power', 'backup']),
action: string().required().oneOf(['command', 'power', 'backup', 'delete_files']),
payload: string().when('action', {
is: (v) => v !== 'backup',
then: string().required('A task payload must be provided.'),
@ -131,6 +131,7 @@ const TaskDetailsModal = ({ schedule, task }: Props) => {
<option value={'command'}>Send command</option>
<option value={'power'}>Send power action</option>
<option value={'backup'}>Create backup</option>
<option value={'delete_files'}>Delete files</option>
</FormikField>
</FormikFieldWrapper>
</div>
@ -166,7 +167,7 @@ const TaskDetailsModal = ({ schedule, task }: Props) => {
</FormikField>
</FormikFieldWrapper>
</div>
) : (
) : values.action === 'backup' ? (
<div>
<Label>Ignored Files</Label>
<FormikFieldWrapper
@ -178,6 +179,16 @@ const TaskDetailsModal = ({ schedule, task }: Props) => {
<FormikField as={Textarea} name={'payload'} rows={6} />
</FormikFieldWrapper>
</div>
) : (
<div>
<Label>Files to Delete</Label>
<FormikFieldWrapper
name={'payload'}
description={'Specify the files that will be deleted. (Whitelist)'}
>
<FormikField as={Textarea} name={'payload'} rows={6} />
</FormikFieldWrapper>
</div>
)}
</div>
<div css={tw`mt-6 bg-neutral-700 border border-neutral-800 shadow-inner p-4 rounded`}>