diff --git a/app/Http/Controllers/Admin/RolesController.php b/app/Http/Controllers/Admin/RolesController.php new file mode 100644 index 000000000..085db3d1a --- /dev/null +++ b/app/Http/Controllers/Admin/RolesController.php @@ -0,0 +1,75 @@ +repository = $repository; + } + + /** + * Returns an array of all roles. + * + * @return \Illuminate\Http\JsonResponse + */ + public function index() + { + return new JsonResponse($this->repository->all()); + } + + /** + * Creates a new role. + * + * @param \Pterodactyl\Http\Requests\Admin\RoleFormRequest $request + * + * @return \Illuminate\Http\JsonResponse + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + public function create(RoleFormRequest $request) + { + $role = $this->repository->create($request->normalize()); + + return new JsonResponse($role); + } + + /** + * Updates a role. + * + * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\Response + */ + public function update() + { + return response('', 204); + } + + /** + * Deletes a role. + * + * @param int $role_id + * + * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\Response + */ + public function delete(int $role_id) + { + $this->repository->delete($role_id); + + return response('', 204); + } +} diff --git a/app/Http/Requests/Admin/RoleFormRequest.php b/app/Http/Requests/Admin/RoleFormRequest.php new file mode 100644 index 000000000..c672b017c --- /dev/null +++ b/app/Http/Requests/Admin/RoleFormRequest.php @@ -0,0 +1,29 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Http\Requests\Admin; + +use Pterodactyl\Models\AdminRole; + +class RoleFormRequest extends AdminFormRequest +{ + /** + * Setup the validation rules to use for these requests. + * + * @return array + */ + public function rules() + { + if ($this->method() === 'PATCH') { + return AdminRole::getRulesForUpdate($this->route()->parameter('mount')->id); + } + + return AdminRole::getRules(); + } +} diff --git a/app/Models/AdminRole.php b/app/Models/AdminRole.php new file mode 100644 index 000000000..76b22478c --- /dev/null +++ b/app/Models/AdminRole.php @@ -0,0 +1,43 @@ + 'required|string|max:64', + 'description' => 'nullable|string|max:255', + ]; +} diff --git a/app/Repositories/Eloquent/AdminRolesRepository.php b/app/Repositories/Eloquent/AdminRolesRepository.php new file mode 100644 index 000000000..7994b6407 --- /dev/null +++ b/app/Repositories/Eloquent/AdminRolesRepository.php @@ -0,0 +1,18 @@ +id(); + $table->string('name', 64); + $table->string('description', 255)->nullable(); + $table->integer('sort_id'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('admin_roles'); + } +} diff --git a/resources/scripts/api/admin/roles/getRoles.ts b/resources/scripts/api/admin/roles/getRoles.ts new file mode 100644 index 000000000..cd2e51901 --- /dev/null +++ b/resources/scripts/api/admin/roles/getRoles.ts @@ -0,0 +1,15 @@ +import http from '@/api/http'; + +export interface Role { + id: number, + name: string, + description: string|null, +} + +export default (): Promise => { + return new Promise((resolve, reject) => { + http.get('/admin/roles') + .then(({ data }) => resolve(data || [])) + .catch(reject); + }); +}; diff --git a/resources/scripts/components/admin/api/ApiKeysContainer.tsx b/resources/scripts/components/admin/api/ApiKeysContainer.tsx index 584b4d4c8..8698c079b 100644 --- a/resources/scripts/components/admin/api/ApiKeysContainer.tsx +++ b/resources/scripts/components/admin/api/ApiKeysContainer.tsx @@ -1,12 +1,22 @@ -import React, { useState } from 'react'; +import NewApiKeyButton from '@/components/admin/api/NewApiKeyButton'; +import React, { useEffect, useState } from 'react'; import tw from 'twin.macro'; import AdminContentBlock from '@/components/admin/AdminContentBlock'; -import Button from '@/components/elements/Button'; import Spinner from '@/components/elements/Spinner'; +interface Key { + id: number, +} + export default () => { - const [ loading ] = useState(false); - const [ keys ] = useState([]); + const [ loading, setLoading ] = useState(true); + const [ keys ] = useState([]); + + useEffect(() => { + setTimeout(() => { + setLoading(false); + }, 500); + }); return ( @@ -16,16 +26,14 @@ export default () => {

Control access credentials for managing this Panel via the API.

- +
{ loading ?
- +
: keys.length < 1 ? diff --git a/resources/scripts/components/admin/api/NewApiKeyButton.tsx b/resources/scripts/components/admin/api/NewApiKeyButton.tsx new file mode 100644 index 000000000..fce62f7c4 --- /dev/null +++ b/resources/scripts/components/admin/api/NewApiKeyButton.tsx @@ -0,0 +1,173 @@ +import Button from '@/components/elements/Button'; +import Field from '@/components/elements/Field'; +import Modal from '@/components/elements/Modal'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import useFlash from '@/plugins/useFlash'; +import { Form, Formik, FormikHelpers } from 'formik'; +import React, { useState } from 'react'; +import tw from 'twin.macro'; +import { object } from 'yup'; + +interface Values { + description: string, +} + +const schema = object().shape({ + +}); + +export default () => { + const [ visible, setVisible ] = useState(false); + const { clearFlashes } = useFlash(); + + const submit = (values: Values, { setSubmitting }: FormikHelpers) => { + clearFlashes('api:create'); + + console.log(values); + setSubmitting(true); + + setTimeout(() => { + setVisible(false); + }, 500); + }; + + return ( + <> + + { + ({ isSubmitting, resetForm }) => ( + { + resetForm(); + setVisible(false); + }} + > + +

New API Key

+
+ + +
+
+

Permissions

+ +
+ None + Read + Write +
+
+ +
+
+

Allocations

+ +
+
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+

Databases

+ +
+
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+

Eggs

+ +
+
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+

Locations

+ +
+
+ +
+ +
+ +
+ +
+ +
+
+
+
+
+ +
+ + +
+ +
+ ) + } +
+ + + + ); +}; diff --git a/resources/scripts/components/admin/mounts/MountsContainer.tsx b/resources/scripts/components/admin/mounts/MountsContainer.tsx index f475cfe3e..9ed8b7364 100644 --- a/resources/scripts/components/admin/mounts/MountsContainer.tsx +++ b/resources/scripts/components/admin/mounts/MountsContainer.tsx @@ -1,3 +1,4 @@ +import Button from '@/components/elements/Button'; import React from 'react'; import tw from 'twin.macro'; import AdminContentBlock from '@/components/admin/AdminContentBlock'; @@ -5,9 +6,15 @@ import AdminContentBlock from '@/components/admin/AdminContentBlock'; export default () => { return ( -
-

Mounts

-

Configure and manage additional mount points for servers.

+
+
+

Mounts

+

Configure and manage additional mount points for servers.

+
+ +
); diff --git a/resources/scripts/components/admin/nests/NestsContainer.tsx b/resources/scripts/components/admin/nests/NestsContainer.tsx index a694f6332..4938c5e86 100644 --- a/resources/scripts/components/admin/nests/NestsContainer.tsx +++ b/resources/scripts/components/admin/nests/NestsContainer.tsx @@ -1,3 +1,4 @@ +import Button from '@/components/elements/Button'; import React from 'react'; import tw from 'twin.macro'; import AdminContentBlock from '@/components/admin/AdminContentBlock'; @@ -5,9 +6,15 @@ import AdminContentBlock from '@/components/admin/AdminContentBlock'; export default () => { return ( -
-

Nests

-

All nests currently available on this system.

+
+
+

Nests

+

All nests currently available on this system.

+
+ +
); diff --git a/resources/scripts/components/admin/nodes/NodesContainer.tsx b/resources/scripts/components/admin/nodes/NodesContainer.tsx index 6038e3a41..e876dbe4f 100644 --- a/resources/scripts/components/admin/nodes/NodesContainer.tsx +++ b/resources/scripts/components/admin/nodes/NodesContainer.tsx @@ -1,3 +1,4 @@ +import Button from '@/components/elements/Button'; import React from 'react'; import tw from 'twin.macro'; import AdminContentBlock from '@/components/admin/AdminContentBlock'; @@ -5,9 +6,15 @@ import AdminContentBlock from '@/components/admin/AdminContentBlock'; export default () => { return ( -
-

Nodes

-

All nodes available on the system.

+
+
+

Nodes

+

All nodes available on the system.

+
+ +
); diff --git a/resources/scripts/components/admin/roles/RolesContainer.tsx b/resources/scripts/components/admin/roles/RolesContainer.tsx new file mode 100644 index 000000000..e5832d158 --- /dev/null +++ b/resources/scripts/components/admin/roles/RolesContainer.tsx @@ -0,0 +1,99 @@ +import { httpErrorToHuman } from '@/api/http'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import useFlash from '@/plugins/useFlash'; +import React, { useEffect, useState } from 'react'; +import Button from '@/components/elements/Button'; +import tw from 'twin.macro'; +import AdminContentBlock from '@/components/admin/AdminContentBlock'; +import Spinner from '@/components/elements/Spinner'; +import getRoles, { Role } from '@/api/admin/roles/getRoles'; + +export default () => { + const { clearFlashes, addError } = useFlash(); + const [ loading, setLoading ] = useState(true); + const [ roles, setRoles ] = useState([]); + + useEffect(() => { + clearFlashes('roles'); + + getRoles() + .then(roles => setRoles(roles)) + .catch(error => { + addError({ message: httpErrorToHuman(error), key: 'roles' }); + console.error(error); + }) + .then(() => setLoading(false)); + }, []); + + return ( + +
+
+

Roles

+

Soon™

+
+ + +
+ + + +
+
+ { loading ? +
+ +
+ : + roles.length < 1 ? +
+
+ {'No +
+ +

No items could be found, it's almost like they are hiding.

+
+ : +
+ + + + + + + + + + + + + { + roles.map(role => ( + + + + + + )) + } + +
+ ID + + Name + + Description +
{role.id}{role.name}{role.description}
+ +
+ +
+
+ } +
+
+
+ ); +}; diff --git a/resources/scripts/components/admin/servers/ServersContainer.tsx b/resources/scripts/components/admin/servers/ServersContainer.tsx index b175303ed..350387279 100644 --- a/resources/scripts/components/admin/servers/ServersContainer.tsx +++ b/resources/scripts/components/admin/servers/ServersContainer.tsx @@ -1,3 +1,4 @@ +import Button from '@/components/elements/Button'; import React from 'react'; import tw from 'twin.macro'; import AdminContentBlock from '@/components/admin/AdminContentBlock'; @@ -5,9 +6,15 @@ import AdminContentBlock from '@/components/admin/AdminContentBlock'; export default () => { return ( -
-

Servers

-

All servers available on the system.

+
+
+

Servers

+

All servers available on the system.

+
+ +
); diff --git a/resources/scripts/components/admin/users/UsersContainer.tsx b/resources/scripts/components/admin/users/UsersContainer.tsx index 76375beb0..4ceb6420a 100644 --- a/resources/scripts/components/admin/users/UsersContainer.tsx +++ b/resources/scripts/components/admin/users/UsersContainer.tsx @@ -1,3 +1,4 @@ +import Button from '@/components/elements/Button'; import React from 'react'; import tw from 'twin.macro'; import AdminContentBlock from '@/components/admin/AdminContentBlock'; @@ -5,9 +6,15 @@ import AdminContentBlock from '@/components/admin/AdminContentBlock'; export default () => { return ( -
-

Users

-

All registered users on the system.

+
+
+

Users

+

All registered users on the system.

+
+ +
); diff --git a/resources/scripts/routers/AdminRouter.tsx b/resources/scripts/routers/AdminRouter.tsx index e4972d3ec..be50a11a7 100644 --- a/resources/scripts/routers/AdminRouter.tsx +++ b/resources/scripts/routers/AdminRouter.tsx @@ -1,3 +1,4 @@ +import RolesContainer from '@/components/admin/roles/RolesContainer'; import React, { useState } from 'react'; import { NavLink, Route, RouteComponentProps, Switch } from 'react-router-dom'; import NotFound from '@/components/screens/NotFound'; @@ -126,6 +127,10 @@ export default ({ location, match }: RouteComponentProps) => { Users + + + Roles + Service Management @@ -144,7 +149,7 @@ export default ({ location, match }: RouteComponentProps) => {
- Profile Picture + Profile Picture
Matthew Penner @@ -170,6 +175,7 @@ export default ({ location, match }: RouteComponentProps) => { + diff --git a/routes/admin.php b/routes/admin.php index fd70d8184..d0e3286f1 100644 --- a/routes/admin.php +++ b/routes/admin.php @@ -3,7 +3,7 @@ use Illuminate\Support\Facades\Route; Route::get('/', 'BaseController@index')->name('admin.index')->fallback(); -Route::get('/{react}', 'BaseController@index'); +//Route::get('/{react}', 'BaseController@index'); Route::get('/statistics', 'StatisticsController@index')->name('admin.statistics'); @@ -16,8 +16,8 @@ Route::get('/statistics', 'StatisticsController@index')->name('admin.statistics' | */ Route::group(['prefix' => 'api'], function () { - //Route::get('/', 'ApiController@index')->name('admin.api.index'); - //Route::get('/new', 'ApiController@create')->name('admin.api.new'); + Route::get('/', 'ApiController@index')->name('admin.api.index'); + Route::get('/new', 'ApiController@create')->name('admin.api.new'); Route::post('/new', 'ApiController@store'); @@ -33,8 +33,8 @@ Route::group(['prefix' => 'api'], function () { | */ Route::group(['prefix' => 'locations'], function () { - //Route::get('/', 'LocationController@index')->name('admin.locations'); - //Route::get('/view/{location}', 'LocationController@view')->name('admin.locations.view'); + Route::get('/', 'LocationController@index')->name('admin.locations'); + Route::get('/view/{location}', 'LocationController@view')->name('admin.locations.view'); Route::post('/', 'LocationController@create'); Route::patch('/view/{location}', 'LocationController@update'); @@ -49,8 +49,8 @@ Route::group(['prefix' => 'locations'], function () { | */ Route::group(['prefix' => 'databases'], function () { - //Route::get('/', 'DatabaseController@index')->name('admin.databases'); - //Route::get('/view/{host}', 'DatabaseController@view')->name('admin.databases.view'); + Route::get('/', 'DatabaseController@index')->name('admin.databases'); + Route::get('/view/{host}', 'DatabaseController@view')->name('admin.databases.view'); Route::post('/', 'DatabaseController@create'); Route::patch('/view/{host}', 'DatabaseController@update'); @@ -66,10 +66,10 @@ Route::group(['prefix' => 'databases'], function () { | */ Route::group(['prefix' => 'settings'], function () { - //Route::get('/', 'Settings\IndexController@index')->name('admin.settings'); - //Route::get('/mail', 'Settings\MailController@index')->name('admin.settings.mail'); - //Route::get('/mail/test', 'Settings\MailController@test')->name('admin.settings.mail.test'); - //Route::get('/advanced', 'Settings\AdvancedController@index')->name('admin.settings.advanced'); + Route::get('/', 'Settings\IndexController@index')->name('admin.settings'); + Route::get('/mail', 'Settings\MailController@index')->name('admin.settings.mail'); + Route::get('/mail/test', 'Settings\MailController@test')->name('admin.settings.mail.test'); + Route::get('/advanced', 'Settings\AdvancedController@index')->name('admin.settings.advanced'); Route::patch('/', 'Settings\IndexController@update'); Route::patch('/mail', 'Settings\MailController@update'); @@ -85,10 +85,10 @@ Route::group(['prefix' => 'settings'], function () { | */ Route::group(['prefix' => 'users'], function () { - //Route::get('/', 'UserController@index')->name('admin.users'); - //Route::get('/accounts.json', 'UserController@json')->name('admin.users.json'); - //Route::get('/new', 'UserController@create')->name('admin.users.new'); - //Route::get('/view/{user}', 'UserController@view')->name('admin.users.view'); + Route::get('/', 'UserController@index')->name('admin.users'); + Route::get('/accounts.json', 'UserController@json')->name('admin.users.json'); + Route::get('/new', 'UserController@create')->name('admin.users.new'); + Route::get('/view/{user}', 'UserController@view')->name('admin.users.view'); Route::post('/new', 'UserController@store'); Route::patch('/view/{user}', 'UserController@update'); @@ -105,20 +105,20 @@ Route::group(['prefix' => 'users'], function () { | */ Route::group(['prefix' => 'servers'], function () { - //Route::get('/', 'Servers\ServerController@index')->name('admin.servers'); - //Route::get('/new', 'Servers\CreateServerController@index')->name('admin.servers.new'); - //Route::get('/view/{server}', 'Servers\ServerViewController@index')->name('admin.servers.view'); + Route::get('/', 'Servers\ServerController@index')->name('admin.servers'); + Route::get('/new', 'Servers\CreateServerController@index')->name('admin.servers.new'); + Route::get('/view/{server}', 'Servers\ServerViewController@index')->name('admin.servers.view'); - /*Route::group(['middleware' => [ServerInstalled::class]], function () { + Route::group(['middleware' => [ServerInstalled::class]], function () { Route::get('/view/{server}/details', 'Servers\ServerViewController@details')->name('admin.servers.view.details'); Route::get('/view/{server}/build', 'Servers\ServerViewController@build')->name('admin.servers.view.build'); Route::get('/view/{server}/startup', 'Servers\ServerViewController@startup')->name('admin.servers.view.startup'); Route::get('/view/{server}/database', 'Servers\ServerViewController@database')->name('admin.servers.view.database'); Route::get('/view/{server}/mounts', 'Servers\ServerViewController@mounts')->name('admin.servers.view.mounts'); - });*/ + }); - //Route::get('/view/{server}/manage', 'Servers\ServerViewController@manage')->name('admin.servers.view.manage'); - //Route::get('/view/{server}/delete', 'Servers\ServerViewController@delete')->name('admin.servers.view.delete'); + Route::get('/view/{server}/manage', 'Servers\ServerViewController@manage')->name('admin.servers.view.manage'); + Route::get('/view/{server}/delete', 'Servers\ServerViewController@delete')->name('admin.servers.view.delete'); Route::post('/new', 'Servers\CreateServerController@store'); Route::post('/view/{server}/build', 'ServersController@updateBuild'); @@ -147,15 +147,15 @@ Route::group(['prefix' => 'servers'], function () { | */ Route::group(['prefix' => 'nodes'], function () { - //Route::get('/', 'Nodes\NodeController@index')->name('admin.nodes'); - //Route::get('/new', 'NodesController@create')->name('admin.nodes.new'); - //Route::get('/view/{node}', 'Nodes\NodeViewController@index')->name('admin.nodes.view'); - //Route::get('/view/{node}/settings', 'Nodes\NodeViewController@settings')->name('admin.nodes.view.settings'); - //Route::get('/view/{node}/configuration', 'Nodes\NodeViewController@configuration')->name('admin.nodes.view.configuration'); - //Route::get('/view/{node}/allocation', 'Nodes\NodeViewController@allocations')->name('admin.nodes.view.allocation'); - //Route::get('/view/{node}/servers', 'Nodes\NodeViewController@servers')->name('admin.nodes.view.servers'); - //Route::get('/view/{node}/system-information', 'Nodes\SystemInformationController'); - //Route::get('/view/{node}/settings/token', 'NodeAutoDeployController')->name('admin.nodes.view.configuration.token'); + Route::get('/', 'Nodes\NodeController@index')->name('admin.nodes'); + Route::get('/new', 'NodesController@create')->name('admin.nodes.new'); + Route::get('/view/{node}', 'Nodes\NodeViewController@index')->name('admin.nodes.view'); + Route::get('/view/{node}/settings', 'Nodes\NodeViewController@settings')->name('admin.nodes.view.settings'); + Route::get('/view/{node}/configuration', 'Nodes\NodeViewController@configuration')->name('admin.nodes.view.configuration'); + Route::get('/view/{node}/allocation', 'Nodes\NodeViewController@allocations')->name('admin.nodes.view.allocation'); + Route::get('/view/{node}/servers', 'Nodes\NodeViewController@servers')->name('admin.nodes.view.servers'); + Route::get('/view/{node}/system-information', 'Nodes\SystemInformationController'); + Route::get('/view/{node}/settings/token', 'NodeAutoDeployController')->name('admin.nodes.view.configuration.token'); Route::post('/new', 'NodesController@store'); Route::post('/view/{node}/allocation', 'NodesController@createAllocation'); @@ -178,8 +178,8 @@ Route::group(['prefix' => 'nodes'], function () { | */ Route::group(['prefix' => 'mounts'], function () { - //Route::get('/', 'MountController@index')->name('admin.mounts'); - //Route::get('/view/{mount}', 'MountController@view')->name('admin.mounts.view'); + Route::get('/', 'MountController@index')->name('admin.mounts'); + Route::get('/view/{mount}', 'MountController@view')->name('admin.mounts.view'); Route::post('/', 'MountController@create'); Route::post('/{mount}/eggs', 'MountController@addEggs')->name('admin.mounts.eggs'); @@ -200,14 +200,14 @@ Route::group(['prefix' => 'mounts'], function () { | */ Route::group(['prefix' => 'nests'], function () { - //Route::get('/', 'Nests\NestController@index')->name('admin.nests'); - //Route::get('/new', 'Nests\NestController@create')->name('admin.nests.new'); - //Route::get('/view/{nest}', 'Nests\NestController@view')->name('admin.nests.view'); - //Route::get('/egg/new', 'Nests\EggController@create')->name('admin.nests.egg.new'); - //Route::get('/egg/{egg}', 'Nests\EggController@view')->name('admin.nests.egg.view'); - //Route::get('/egg/{egg}/export', 'Nests\EggShareController@export')->name('admin.nests.egg.export'); - //Route::get('/egg/{egg}/variables', 'Nests\EggVariableController@view')->name('admin.nests.egg.variables'); - //Route::get('/egg/{egg}/scripts', 'Nests\EggScriptController@index')->name('admin.nests.egg.scripts'); + Route::get('/', 'Nests\NestController@index')->name('admin.nests'); + Route::get('/new', 'Nests\NestController@create')->name('admin.nests.new'); + Route::get('/view/{nest}', 'Nests\NestController@view')->name('admin.nests.view'); + Route::get('/egg/new', 'Nests\EggController@create')->name('admin.nests.egg.new'); + Route::get('/egg/{egg}', 'Nests\EggController@view')->name('admin.nests.egg.view'); + Route::get('/egg/{egg}/export', 'Nests\EggShareController@export')->name('admin.nests.egg.export'); + Route::get('/egg/{egg}/variables', 'Nests\EggVariableController@view')->name('admin.nests.egg.variables'); + Route::get('/egg/{egg}/scripts', 'Nests\EggScriptController@index')->name('admin.nests.egg.scripts'); Route::post('/new', 'Nests\NestController@store'); Route::post('/import', 'Nests\EggShareController@import')->name('admin.nests.egg.import'); @@ -225,3 +225,13 @@ Route::group(['prefix' => 'nests'], function () { Route::delete('/egg/{egg}', 'Nests\EggController@destroy'); Route::delete('/egg/{egg}/variables/{variable}', 'Nests\EggVariableController@destroy'); }); + +Route::group(['prefix' => 'roles'], function () { + Route::get('/', 'RolesController@index')->name('admin.roles'); + + Route::post('/', 'RolesController@create'); + + Route::patch('/', 'RolesController@update'); + + Route::delete('/', 'RolesController@delete'); +}); diff --git a/routes/auth.php b/routes/auth.php index 4bdb72206..6e579796a 100644 --- a/routes/auth.php +++ b/routes/auth.php @@ -1,5 +1,7 @@ .