Very basic implemention of frontend logic required to display backups and create a new one
This commit is contained in:
parent
17ec4efd3b
commit
9991989f89
16 changed files with 431 additions and 2 deletions
54
app/Http/Controllers/Api/Client/Servers/BackupController.php
Normal file
54
app/Http/Controllers/Api/Client/Servers/BackupController.php
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Pterodactyl\Http\Controllers\Api\Client\Servers;
|
||||||
|
|
||||||
|
use Pterodactyl\Models\Server;
|
||||||
|
use Pterodactyl\Transformers\Api\Client\BackupTransformer;
|
||||||
|
use Pterodactyl\Http\Controllers\Api\Client\ClientApiController;
|
||||||
|
use Pterodactyl\Http\Requests\Api\Client\Servers\Backups\GetBackupsRequest;
|
||||||
|
use Pterodactyl\Http\Requests\Api\Client\Servers\Backups\StoreBackupRequest;
|
||||||
|
|
||||||
|
class BackupController extends ClientApiController
|
||||||
|
{
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all of the backups for a given server instance in a paginated
|
||||||
|
* result set.
|
||||||
|
*
|
||||||
|
* @param \Pterodactyl\Http\Requests\Api\Client\Servers\Backups\GetBackupsRequest $request
|
||||||
|
* @param \Pterodactyl\Models\Server $server
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function index(GetBackupsRequest $request, Server $server)
|
||||||
|
{
|
||||||
|
return $this->fractal->collection($server->backups()->paginate(20))
|
||||||
|
->transformWith($this->getTransformer(BackupTransformer::class))
|
||||||
|
->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the backup process for a server.
|
||||||
|
*
|
||||||
|
* @param \Pterodactyl\Http\Requests\Api\Client\Servers\Backups\StoreBackupRequest $request
|
||||||
|
* @param \Pterodactyl\Models\Server $server
|
||||||
|
*/
|
||||||
|
public function store(StoreBackupRequest $request, Server $server)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function view()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Pterodactyl\Http\Requests\Api\Client\Servers\Backups;
|
||||||
|
|
||||||
|
use Pterodactyl\Models\Permission;
|
||||||
|
use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest;
|
||||||
|
|
||||||
|
class GetBackupsRequest extends ClientApiRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function permission()
|
||||||
|
{
|
||||||
|
return Permission::ACTION_BACKUP_READ;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Pterodactyl\Http\Requests\Api\Client\Servers\Backups;
|
||||||
|
|
||||||
|
use Pterodactyl\Models\Permission;
|
||||||
|
use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest;
|
||||||
|
|
||||||
|
class StoreBackupRequest extends ClientApiRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function permission()
|
||||||
|
{
|
||||||
|
return Permission::ACTION_BACKUP_CREATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'name' => 'nullable|string|max:255',
|
||||||
|
'ignore' => 'nullable|string',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,9 +6,10 @@ use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property int $id
|
* @property int $id
|
||||||
|
* @property int $server_id
|
||||||
* @property int $uuid
|
* @property int $uuid
|
||||||
* @property string $name
|
* @property string $name
|
||||||
* @property string $contents
|
* @property string $ignore
|
||||||
* @property string $disk
|
* @property string $disk
|
||||||
* @property string|null $sha256_hash
|
* @property string|null $sha256_hash
|
||||||
* @property int $bytes
|
* @property int $bytes
|
||||||
|
@ -16,11 +17,15 @@ use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
* @property \Carbon\CarbonImmutable $created_at
|
* @property \Carbon\CarbonImmutable $created_at
|
||||||
* @property \Carbon\CarbonImmutable $updated_at
|
* @property \Carbon\CarbonImmutable $updated_at
|
||||||
* @property \Carbon\CarbonImmutable|null $deleted_at
|
* @property \Carbon\CarbonImmutable|null $deleted_at
|
||||||
|
*
|
||||||
|
* @property \Pterodactyl\Models\Server $server
|
||||||
*/
|
*/
|
||||||
class Backup extends Model
|
class Backup extends Model
|
||||||
{
|
{
|
||||||
use SoftDeletes;
|
use SoftDeletes;
|
||||||
|
|
||||||
|
const RESOURCE_NAME = 'backup';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
|
@ -56,4 +61,12 @@ class Backup extends Model
|
||||||
{
|
{
|
||||||
return $this->asImmutableDateTime($value);
|
return $this->asImmutableDateTime($value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||||
|
*/
|
||||||
|
public function server()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Server::class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,12 @@ class Permission extends Model
|
||||||
const ACTION_USER_UPDATE = 'user.update';
|
const ACTION_USER_UPDATE = 'user.update';
|
||||||
const ACTION_USER_DELETE = 'user.delete';
|
const ACTION_USER_DELETE = 'user.delete';
|
||||||
|
|
||||||
|
const ACTION_BACKUP_READ = 'backup.read';
|
||||||
|
const ACTION_BACKUP_CREATE = 'backup.create';
|
||||||
|
const ACTION_BACKUP_UPDATE = 'backup.update';
|
||||||
|
const ACTION_BACKUP_DELETE = 'backup.delete';
|
||||||
|
const ACTION_BACKUP_DOWNLOAD = 'backup.download';
|
||||||
|
|
||||||
const ACTION_ALLOCATION_READ = 'allocation.read';
|
const ACTION_ALLOCATION_READ = 'allocation.read';
|
||||||
const ACTION_ALLOCIATION_UPDATE = 'allocation.update';
|
const ACTION_ALLOCIATION_UPDATE = 'allocation.update';
|
||||||
|
|
||||||
|
@ -135,6 +141,17 @@ class Permission extends Model
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
'backup' => [
|
||||||
|
'description' => 'Permissions that control a user\'s ability to generate and manage server backups.',
|
||||||
|
'keys' => [
|
||||||
|
'create' => 'Allows a user to create new backups for this server.',
|
||||||
|
'read' => 'Allows a user to view all backups that exist for this server.',
|
||||||
|
'update' => '',
|
||||||
|
'delete' => 'Allows a user to remove backups from the system.',
|
||||||
|
'download' => 'Allows a user to download backups.',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
// Controls permissions for editing or viewing a server's allocations.
|
// Controls permissions for editing or viewing a server's allocations.
|
||||||
'allocation' => [
|
'allocation' => [
|
||||||
'description' => 'Permissions that control a user\'s ability to modify the port allocations for this server.',
|
'description' => 'Permissions that control a user\'s ability to modify the port allocations for this server.',
|
||||||
|
|
|
@ -51,6 +51,7 @@ use Znck\Eloquent\Traits\BelongsToThrough;
|
||||||
* @property \Pterodactyl\Models\Location $location
|
* @property \Pterodactyl\Models\Location $location
|
||||||
* @property \Pterodactyl\Models\DaemonKey $key
|
* @property \Pterodactyl\Models\DaemonKey $key
|
||||||
* @property \Pterodactyl\Models\DaemonKey[]|\Illuminate\Database\Eloquent\Collection $keys
|
* @property \Pterodactyl\Models\DaemonKey[]|\Illuminate\Database\Eloquent\Collection $keys
|
||||||
|
* @property \Pterodactyl\Models\Backup[]|\Illuminate\Database\Eloquent\Collection $backups
|
||||||
*/
|
*/
|
||||||
class Server extends Model
|
class Server extends Model
|
||||||
{
|
{
|
||||||
|
@ -339,4 +340,12 @@ class Server extends Model
|
||||||
{
|
{
|
||||||
return $this->hasMany(DaemonKey::class);
|
return $this->hasMany(DaemonKey::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||||
|
*/
|
||||||
|
public function backups()
|
||||||
|
{
|
||||||
|
return $this->hasMany(Backup::class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
33
app/Transformers/Api/Client/BackupTransformer.php
Normal file
33
app/Transformers/Api/Client/BackupTransformer.php
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Pterodactyl\Transformers\Api\Client;
|
||||||
|
|
||||||
|
use Pterodactyl\Models\Backup;
|
||||||
|
|
||||||
|
class BackupTransformer extends BaseClientTransformer
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getResourceName(): string
|
||||||
|
{
|
||||||
|
return Backup::RESOURCE_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \Pterodactyl\Models\Backup $backup
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function transform(Backup $backup)
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'uuid' => $backup->uuid,
|
||||||
|
'name' => $backup->name,
|
||||||
|
'ignore' => $backup->ignore,
|
||||||
|
'sha256_hash' => $backup->sha256_hash,
|
||||||
|
'bytes' => $backup->bytes,
|
||||||
|
'created_at' => $backup->created_at->toIso8601String(),
|
||||||
|
'completed_at' => $backup->completed_at->toIso8601String(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,15 +15,19 @@ class CreateBackupsTable extends Migration
|
||||||
{
|
{
|
||||||
Schema::create('backups', function (Blueprint $table) {
|
Schema::create('backups', function (Blueprint $table) {
|
||||||
$table->bigIncrements('id');
|
$table->bigIncrements('id');
|
||||||
|
$table->unsignedInteger('server_id');
|
||||||
$table->char('uuid', 36);
|
$table->char('uuid', 36);
|
||||||
$table->string('name');
|
$table->string('name');
|
||||||
$table->text('contents');
|
$table->text('ignored');
|
||||||
$table->string('disk');
|
$table->string('disk');
|
||||||
$table->string('sha256_hash')->nullable();
|
$table->string('sha256_hash')->nullable();
|
||||||
$table->integer('bytes')->default(0);
|
$table->integer('bytes')->default(0);
|
||||||
$table->timestamp('completed_at')->nullable();
|
$table->timestamp('completed_at')->nullable();
|
||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
$table->softDeletes();
|
$table->softDeletes();
|
||||||
|
|
||||||
|
$table->unique('uuid');
|
||||||
|
$table->foreign('server_id')->references('id')->on('servers')->onDelete('cascade');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
12
resources/scripts/api/server/backups/createServerBackup.ts
Normal file
12
resources/scripts/api/server/backups/createServerBackup.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import { rawDataToServerBackup, ServerBackup } from '@/api/server/backups/getServerBackups';
|
||||||
|
import http from '@/api/http';
|
||||||
|
|
||||||
|
export default (uuid: string, name?: string, ignore?: string): Promise<ServerBackup> => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
http.post(`/api/client/servers/${uuid}/backups`, {
|
||||||
|
name, ignore,
|
||||||
|
})
|
||||||
|
.then(({ data }) => resolve(rawDataToServerBackup(data.attributes)))
|
||||||
|
.catch(reject);
|
||||||
|
});
|
||||||
|
};
|
32
resources/scripts/api/server/backups/getServerBackups.ts
Normal file
32
resources/scripts/api/server/backups/getServerBackups.ts
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import http, { FractalResponseData, getPaginationSet, PaginatedResult } from '@/api/http';
|
||||||
|
|
||||||
|
export interface ServerBackup {
|
||||||
|
uuid: string;
|
||||||
|
name: string;
|
||||||
|
contents: string;
|
||||||
|
sha256Hash: string;
|
||||||
|
bytes: number;
|
||||||
|
createdAt: Date;
|
||||||
|
completedAt: Date | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const rawDataToServerBackup = ({ attributes }: FractalResponseData): ServerBackup => ({
|
||||||
|
uuid: attributes.uuid,
|
||||||
|
name: attributes.name,
|
||||||
|
contents: attributes.contents,
|
||||||
|
sha256Hash: attributes.sha256_hash,
|
||||||
|
bytes: attributes.bytes,
|
||||||
|
createdAt: new Date(attributes.created_at),
|
||||||
|
completedAt: attributes.completed_at ? new Date(attributes.completed_at) : null,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default (uuid: string, page?: number | string): Promise<PaginatedResult<ServerBackup>> => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
http.get(`/api/client/servers/${uuid}/backups`, { params: { page } })
|
||||||
|
.then(({ data }) => resolve({
|
||||||
|
items: (data.data || []).map(rawDataToServerBackup),
|
||||||
|
pagination: getPaginationSet(data.meta.pagination),
|
||||||
|
}))
|
||||||
|
.catch(reject);
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,61 @@
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import Spinner from '@/components/elements/Spinner';
|
||||||
|
import getServerBackups, { ServerBackup } from '@/api/server/backups/getServerBackups';
|
||||||
|
import useServer from '@/plugins/useServer';
|
||||||
|
import useFlash from '@/plugins/useFlash';
|
||||||
|
import { httpErrorToHuman } from '@/api/http';
|
||||||
|
import Can from '@/components/elements/Can';
|
||||||
|
import CreateBackupButton from '@/components/server/backups/CreateBackupButton';
|
||||||
|
import FlashMessageRender from '@/components/FlashMessageRender';
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
const { uuid } = useServer();
|
||||||
|
const { addError, clearFlashes } = useFlash();
|
||||||
|
const [ loading, setLoading ] = useState(true);
|
||||||
|
const [ backups, setBackups ] = useState<ServerBackup[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
clearFlashes('backups');
|
||||||
|
getServerBackups(uuid)
|
||||||
|
.then(data => {
|
||||||
|
setBackups(data.items);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error(error);
|
||||||
|
addError({ key: 'backups', message: httpErrorToHuman(error) });
|
||||||
|
})
|
||||||
|
.then(() => setLoading(false));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return <Spinner size={'large'} centered={true}/>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={'mt-10 mb-6'}>
|
||||||
|
<FlashMessageRender byKey={'backups'} className={'mb-4'}/>
|
||||||
|
{!backups.length ?
|
||||||
|
<p className="text-center text-sm text-neutral-400">
|
||||||
|
There are no backups stored for this server.
|
||||||
|
</p>
|
||||||
|
:
|
||||||
|
<div>
|
||||||
|
{
|
||||||
|
backups.map(backup => (
|
||||||
|
<div key={backup.uuid}>
|
||||||
|
{backup.uuid}
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<Can action={'backup.create'}>
|
||||||
|
<div className={'mt-6 flex justify-end'}>
|
||||||
|
<CreateBackupButton
|
||||||
|
onBackupGenerated={backup => setBackups(s => [...s, backup])}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Can>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,118 @@
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import Modal, { RequiredModalProps } from '@/components/elements/Modal';
|
||||||
|
import { Field as FormikField, Form, Formik, FormikHelpers, useFormikContext } from 'formik';
|
||||||
|
import { object, string } from 'yup';
|
||||||
|
import Field from '@/components/elements/Field';
|
||||||
|
import FormikFieldWrapper from '@/components/elements/FormikFieldWrapper';
|
||||||
|
import useFlash from '@/plugins/useFlash';
|
||||||
|
import useServer from '@/plugins/useServer';
|
||||||
|
import createServerBackup from '@/api/server/backups/createServerBackup';
|
||||||
|
import { httpErrorToHuman } from '@/api/http';
|
||||||
|
import FlashMessageRender from '@/components/FlashMessageRender';
|
||||||
|
import { ServerBackup } from '@/api/server/backups/getServerBackups';
|
||||||
|
|
||||||
|
interface Values {
|
||||||
|
name: string;
|
||||||
|
ignored: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
onBackupGenerated: (backup: ServerBackup) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ModalContent = ({ ...props }: RequiredModalProps) => {
|
||||||
|
const { isSubmitting } = useFormikContext<Values>();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal {...props} showSpinnerOverlay={isSubmitting}>
|
||||||
|
<Form className={'pb-6'}>
|
||||||
|
<FlashMessageRender byKey={'backups:create'} className={'mb-4'}/>
|
||||||
|
<h3 className={'mb-6'}>Create server backup</h3>
|
||||||
|
<div className={'mb-6'}>
|
||||||
|
<Field
|
||||||
|
name={'name'}
|
||||||
|
label={'Backup name'}
|
||||||
|
description={'If provided, the name that should be used to reference this backup.'}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={'mb-6'}>
|
||||||
|
<FormikFieldWrapper
|
||||||
|
name={'ignore'}
|
||||||
|
label={'Ignored Files & Directories'}
|
||||||
|
description={`
|
||||||
|
Enter the files or folders to ignore while generating this backup. Leave blank to use
|
||||||
|
the contents of the .pteroignore file in the root of the server directory if present.
|
||||||
|
Wildcard matching of files and folders is supported in addition to negating a rule by
|
||||||
|
prefixing the path with an exclamation point.
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<FormikField
|
||||||
|
name={'contents'}
|
||||||
|
component={'textarea'}
|
||||||
|
className={'input-dark h-32'}
|
||||||
|
/>
|
||||||
|
</FormikFieldWrapper>
|
||||||
|
</div>
|
||||||
|
<div className={'flex justify-end'}>
|
||||||
|
<button
|
||||||
|
type={'submit'}
|
||||||
|
className={'btn btn-primary btn-sm'}
|
||||||
|
>
|
||||||
|
Start backup
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ({ onBackupGenerated }: Props) => {
|
||||||
|
const { uuid } = useServer();
|
||||||
|
const { addError, clearFlashes } = useFlash();
|
||||||
|
const [ visible, setVisible ] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
clearFlashes('backups:create');
|
||||||
|
}, [visible]);
|
||||||
|
|
||||||
|
const submit = ({ name, ignored }: Values, { setSubmitting }: FormikHelpers<Values>) => {
|
||||||
|
clearFlashes('backups:create')
|
||||||
|
createServerBackup(uuid, name, ignored)
|
||||||
|
.then(backup => {
|
||||||
|
onBackupGenerated(backup);
|
||||||
|
setVisible(false);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error(error);
|
||||||
|
addError({ key: 'backups:create', message: httpErrorToHuman(error) });
|
||||||
|
setSubmitting(false);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{visible &&
|
||||||
|
<Formik
|
||||||
|
onSubmit={submit}
|
||||||
|
initialValues={{ name: '', ignored: '' }}
|
||||||
|
validationSchema={object().shape({
|
||||||
|
name: string().max(255),
|
||||||
|
ignored: string(),
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<ModalContent
|
||||||
|
appear={true}
|
||||||
|
visible={visible}
|
||||||
|
onDismissed={() => setVisible(false)}
|
||||||
|
/>
|
||||||
|
</Formik>
|
||||||
|
}
|
||||||
|
<button
|
||||||
|
className={'btn btn-primary btn-sm'}
|
||||||
|
onClick={() => setVisible(true)}
|
||||||
|
>
|
||||||
|
Create backup
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
9
resources/scripts/plugins/useFlash.ts
Normal file
9
resources/scripts/plugins/useFlash.ts
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import { Actions, useStoreActions } from 'easy-peasy';
|
||||||
|
import { FlashStore } from '@/state/flashes';
|
||||||
|
import { ApplicationStore } from '@/state';
|
||||||
|
|
||||||
|
const useFlash = (): Actions<FlashStore> => {
|
||||||
|
return useStoreActions((actions: Actions<ApplicationStore>) => actions.flashes);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useFlash;
|
9
resources/scripts/plugins/useServer.ts
Normal file
9
resources/scripts/plugins/useServer.ts
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import { DependencyList } from 'react';
|
||||||
|
import { ServerContext } from '@/state/server';
|
||||||
|
import { Server } from '@/api/server/getServer';
|
||||||
|
|
||||||
|
const useServer = (dependencies?: DependencyList): Server => {
|
||||||
|
return ServerContext.useStoreState(state => state.server.data!, [ dependencies ]);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useServer;
|
|
@ -16,6 +16,7 @@ import ScheduleContainer from '@/components/server/schedules/ScheduleContainer';
|
||||||
import ScheduleEditContainer from '@/components/server/schedules/ScheduleEditContainer';
|
import ScheduleEditContainer from '@/components/server/schedules/ScheduleEditContainer';
|
||||||
import UsersContainer from '@/components/server/users/UsersContainer';
|
import UsersContainer from '@/components/server/users/UsersContainer';
|
||||||
import Can from '@/components/elements/Can';
|
import Can from '@/components/elements/Can';
|
||||||
|
import BackupContainer from '@/components/server/backups/BackupContainer';
|
||||||
|
|
||||||
const ServerRouter = ({ match, location }: RouteComponentProps<{ id: string }>) => {
|
const ServerRouter = ({ match, location }: RouteComponentProps<{ id: string }>) => {
|
||||||
const server = ServerContext.useStoreState(state => state.server.data);
|
const server = ServerContext.useStoreState(state => state.server.data);
|
||||||
|
@ -47,6 +48,9 @@ const ServerRouter = ({ match, location }: RouteComponentProps<{ id: string }>)
|
||||||
<Can action={'user.*'}>
|
<Can action={'user.*'}>
|
||||||
<NavLink to={`${match.url}/users`}>Users</NavLink>
|
<NavLink to={`${match.url}/users`}>Users</NavLink>
|
||||||
</Can>
|
</Can>
|
||||||
|
<Can action={'backup.*'}>
|
||||||
|
<NavLink to={`${match.url}/backups`}>Backups</NavLink>
|
||||||
|
</Can>
|
||||||
<Can action={['settings.*', 'file.sftp']} matchAny={true}>
|
<Can action={['settings.*', 'file.sftp']} matchAny={true}>
|
||||||
<NavLink to={`${match.url}/settings`}>Settings</NavLink>
|
<NavLink to={`${match.url}/settings`}>Settings</NavLink>
|
||||||
</Can>
|
</Can>
|
||||||
|
@ -77,6 +81,7 @@ const ServerRouter = ({ match, location }: RouteComponentProps<{ id: string }>)
|
||||||
<Route path={`${match.path}/schedules`} component={ScheduleContainer} exact/>
|
<Route path={`${match.path}/schedules`} component={ScheduleContainer} exact/>
|
||||||
<Route path={`${match.path}/schedules/:id`} component={ScheduleEditContainer} exact/>
|
<Route path={`${match.path}/schedules/:id`} component={ScheduleEditContainer} exact/>
|
||||||
<Route path={`${match.path}/users`} component={UsersContainer} exact/>
|
<Route path={`${match.path}/users`} component={UsersContainer} exact/>
|
||||||
|
<Route path={`${match.path}/backups`} component={BackupContainer} exact/>
|
||||||
<Route path={`${match.path}/settings`} component={SettingsContainer} exact/>
|
<Route path={`${match.path}/settings`} component={SettingsContainer} exact/>
|
||||||
</Switch>
|
</Switch>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
|
|
|
@ -87,6 +87,14 @@ Route::group(['prefix' => '/servers/{server}', 'middleware' => [AuthenticateServ
|
||||||
Route::delete('/{subuser}', 'Servers\SubuserController@delete');
|
Route::delete('/{subuser}', 'Servers\SubuserController@delete');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Route::group(['prefix' => '/backups'], function () {
|
||||||
|
Route::get('/', 'Servers\BackupController@index');
|
||||||
|
Route::post('/', 'Servers\BackupController@store');
|
||||||
|
Route::get('/{backup}', 'Servers\BackupController@view');
|
||||||
|
Route::post('/{backup}', 'Servers\BackupController@update');
|
||||||
|
Route::delete('/{backup}', 'Servers\BackupController@delete');
|
||||||
|
});
|
||||||
|
|
||||||
Route::group(['prefix' => '/settings'], function () {
|
Route::group(['prefix' => '/settings'], function () {
|
||||||
Route::post('/rename', 'Servers\SettingsController@rename');
|
Route::post('/rename', 'Servers\SettingsController@rename');
|
||||||
Route::post('/reinstall', 'Servers\SettingsController@reinstall');
|
Route::post('/reinstall', 'Servers\SettingsController@reinstall');
|
||||||
|
|
Loading…
Reference in a new issue