Skip to content

Commit 9ba6aae

Browse files
committed
Move actions into context menu, add support for deleting a backup
1 parent 2eb6ab4 commit 9ba6aae

File tree

10 files changed

+344
-66
lines changed

10 files changed

+344
-66
lines changed

app/Http/Controllers/Api/Client/Servers/BackupController.php

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,16 @@
22

33
namespace Pterodactyl\Http\Controllers\Api\Client\Servers;
44

5+
use Pterodactyl\Models\Backup;
56
use Pterodactyl\Models\Server;
7+
use Illuminate\Http\JsonResponse;
8+
use Pterodactyl\Services\Backups\DeleteBackupService;
69
use Pterodactyl\Services\Backups\InitiateBackupService;
710
use Pterodactyl\Transformers\Api\Client\BackupTransformer;
811
use Pterodactyl\Http\Controllers\Api\Client\ClientApiController;
912
use Pterodactyl\Http\Requests\Api\Client\Servers\Backups\GetBackupsRequest;
1013
use Pterodactyl\Http\Requests\Api\Client\Servers\Backups\StoreBackupRequest;
14+
use Pterodactyl\Http\Requests\Api\Client\Servers\Backups\DeleteBackupRequest;
1115

1216
class BackupController extends ClientApiController
1317
{
@@ -16,16 +20,23 @@ class BackupController extends ClientApiController
1620
*/
1721
private $initiateBackupService;
1822

23+
/**
24+
* @var \Pterodactyl\Services\Backups\DeleteBackupService
25+
*/
26+
private $deleteBackupService;
27+
1928
/**
2029
* BackupController constructor.
2130
*
31+
* @param \Pterodactyl\Services\Backups\DeleteBackupService $deleteBackupService
2232
* @param \Pterodactyl\Services\Backups\InitiateBackupService $initiateBackupService
2333
*/
24-
public function __construct(InitiateBackupService $initiateBackupService)
34+
public function __construct(DeleteBackupService $deleteBackupService, InitiateBackupService $initiateBackupService)
2535
{
2636
parent::__construct();
2737

2838
$this->initiateBackupService = $initiateBackupService;
39+
$this->deleteBackupService = $deleteBackupService;
2940
}
3041

3142
/**
@@ -50,7 +61,7 @@ public function index(GetBackupsRequest $request, Server $server)
5061
* @param \Pterodactyl\Models\Server $server
5162
* @return array
5263
*
53-
* @throws \Exception
64+
* @throws \Exception|\Throwable
5465
*/
5566
public function store(StoreBackupRequest $request, Server $server)
5667
{
@@ -63,15 +74,36 @@ public function store(StoreBackupRequest $request, Server $server)
6374
->toArray();
6475
}
6576

66-
public function view()
77+
/**
78+
* Returns information about a single backup.
79+
*
80+
* @param \Pterodactyl\Http\Requests\Api\Client\Servers\Backups\GetBackupsRequest $request
81+
* @param \Pterodactyl\Models\Server $server
82+
* @param \Pterodactyl\Models\Backup $backup
83+
* @return array
84+
*/
85+
public function view(GetBackupsRequest $request, Server $server, Backup $backup)
6786
{
87+
return $this->fractal->item($backup)
88+
->transformWith($this->getTransformer(BackupTransformer::class))
89+
->toArray();
6890
}
6991

70-
public function update()
92+
/**
93+
* Deletes a backup from the panel as well as the remote source where it is currently
94+
* being stored.
95+
*
96+
* @param \Pterodactyl\Http\Requests\Api\Client\Servers\Backups\DeleteBackupRequest $request
97+
* @param \Pterodactyl\Models\Server $server
98+
* @param \Pterodactyl\Models\Backup $backup
99+
* @return \Illuminate\Http\JsonResponse
100+
*
101+
* @throws \Throwable
102+
*/
103+
public function delete(DeleteBackupRequest $request, Server $server, Backup $backup)
71104
{
72-
}
105+
$this->deleteBackupService->handle($backup);
73106

74-
public function delete()
75-
{
107+
return JsonResponse::create([], JsonResponse::HTTP_NO_CONTENT);
76108
}
77109
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
namespace Pterodactyl\Http\Requests\Api\Client\Servers\Backups;
4+
5+
use Pterodactyl\Models\Permission;
6+
use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest;
7+
8+
class DeleteBackupRequest extends ClientApiRequest
9+
{
10+
/**
11+
* @return string
12+
*/
13+
public function permission()
14+
{
15+
return Permission::ACTION_BACKUP_DELETE;
16+
}
17+
}

app/Repositories/Wings/DaemonBackupRepository.php

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,22 +39,19 @@ public function backup(Backup $backup): ResponseInterface
3939
}
4040

4141
/**
42-
* Returns a stream of a backup's contents from the Wings instance so that we
43-
* do not need to send the user directly to the Daemon.
42+
* Deletes a backup from the daemon.
4443
*
45-
* @param string $backup
44+
* @param \Pterodactyl\Models\Backup $backup
4645
* @return \Psr\Http\Message\ResponseInterface
47-
*
4846
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
4947
*/
50-
public function getBackup(string $backup): ResponseInterface
48+
public function delete(Backup $backup): ResponseInterface
5149
{
5250
Assert::isInstanceOf($this->server, Server::class);
5351

5452
try {
55-
return $this->getHttpClient()->get(
56-
sprintf('/api/servers/%s/backup/%s', $this->server->uuid, $backup),
57-
['stream' => true]
53+
return $this->getHttpClient()->delete(
54+
sprintf('/api/servers/%s/backup/%s', $this->server->uuid, $backup->uuid)
5855
);
5956
} catch (TransferException $exception) {
6057
throw new DaemonConnectionException($exception);
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
namespace Pterodactyl\Services\Backups;
4+
5+
use Pterodactyl\Models\Backup;
6+
use Illuminate\Database\ConnectionInterface;
7+
use Pterodactyl\Repositories\Eloquent\BackupRepository;
8+
use Pterodactyl\Repositories\Wings\DaemonBackupRepository;
9+
10+
class DeleteBackupService
11+
{
12+
/**
13+
* @var \Pterodactyl\Repositories\Eloquent\BackupRepository
14+
*/
15+
private $repository;
16+
17+
/**
18+
* @var \Pterodactyl\Repositories\Wings\DaemonBackupRepository
19+
*/
20+
private $daemonBackupRepository;
21+
22+
/**
23+
* @var \Illuminate\Database\ConnectionInterface
24+
*/
25+
private $connection;
26+
27+
/**
28+
* DeleteBackupService constructor.
29+
*
30+
* @param \Illuminate\Database\ConnectionInterface $connection
31+
* @param \Pterodactyl\Repositories\Eloquent\BackupRepository $repository
32+
* @param \Pterodactyl\Repositories\Wings\DaemonBackupRepository $daemonBackupRepository
33+
*/
34+
public function __construct(
35+
ConnectionInterface $connection,
36+
BackupRepository $repository,
37+
DaemonBackupRepository $daemonBackupRepository
38+
) {
39+
$this->repository = $repository;
40+
$this->daemonBackupRepository = $daemonBackupRepository;
41+
$this->connection = $connection;
42+
}
43+
44+
/**
45+
* Deletes a backup from the system.
46+
*
47+
* @param \Pterodactyl\Models\Backup $backup
48+
* @throws \Throwable
49+
*/
50+
public function handle(Backup $backup)
51+
{
52+
$this->connection->transaction(function () use ($backup) {
53+
$this->daemonBackupRepository->setServer($backup->server)->delete($backup);
54+
55+
$this->repository->delete($backup->id);
56+
});
57+
}
58+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import http from '@/api/http';
2+
3+
export default (uuid: string, backup: string): Promise<void> => {
4+
return new Promise((resolve, reject) => {
5+
http.delete(`/api/client/servers/${uuid}/backups/${backup}`)
6+
.then(() => resolve())
7+
.catch(reject);
8+
});
9+
};
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import React, { useEffect, useRef, useState } from 'react';
2+
import { CSSTransition } from 'react-transition-group';
3+
import styled from 'styled-components';
4+
5+
interface Props {
6+
children: React.ReactNode;
7+
renderToggle: (onClick: (e: React.MouseEvent<any, MouseEvent>) => void) => React.ReactChild;
8+
}
9+
10+
export const DropdownButtonRow = styled.button<{ danger?: boolean }>`
11+
${tw`p-2 flex items-center rounded w-full text-neutral-500`};
12+
transition: 150ms all ease;
13+
14+
&:hover {
15+
${props => props.danger
16+
? tw`text-red-700 bg-red-100`
17+
: tw`text-neutral-700 bg-neutral-100`
18+
};
19+
}
20+
`;
21+
22+
const DropdownMenu = ({ renderToggle, children }: Props) => {
23+
const menu = useRef<HTMLDivElement>(null);
24+
const [ posX, setPosX ] = useState(0);
25+
const [ visible, setVisible ] = useState(false);
26+
27+
const onClickHandler = (e: React.MouseEvent<any, MouseEvent>) => {
28+
e.preventDefault();
29+
30+
!visible && setPosX(e.clientX);
31+
setVisible(s => !s);
32+
};
33+
34+
const windowListener = (e: MouseEvent) => {
35+
if (e.button === 2 || !visible || !menu.current) {
36+
return;
37+
}
38+
39+
if (e.target === menu.current || menu.current.contains(e.target as Node)) {
40+
return;
41+
}
42+
43+
if (e.target !== menu.current && !menu.current.contains(e.target as Node)) {
44+
setVisible(false);
45+
}
46+
};
47+
48+
useEffect(() => {
49+
if (!visible || !menu.current) {
50+
return;
51+
}
52+
53+
document.addEventListener('click', windowListener);
54+
menu.current.setAttribute(
55+
'style', `left: ${Math.round(posX - menu.current.clientWidth)}px`,
56+
);
57+
58+
return () => {
59+
document.removeEventListener('click', windowListener);
60+
}
61+
}, [ visible ]);
62+
63+
return (
64+
<div>
65+
{renderToggle(onClickHandler)}
66+
<CSSTransition
67+
timeout={250}
68+
in={visible}
69+
unmountOnExit={true}
70+
classNames={'fade'}
71+
>
72+
<div
73+
ref={menu}
74+
onClick={e => {
75+
e.stopPropagation();
76+
setVisible(false);
77+
}}
78+
className={'absolute bg-white p-2 rounded border border-neutral-700 shadow-lg text-neutral-500 min-w-48'}
79+
>
80+
{children}
81+
</div>
82+
</CSSTransition>
83+
</div>
84+
);
85+
};
86+
87+
export default DropdownMenu;

0 commit comments

Comments
 (0)