admin(ui): display dynamic user information on sidebar

This commit is contained in:
Matthew Penner 2021-01-07 10:21:09 -07:00
parent 9eed88b430
commit 2352ef0369
9 changed files with 57 additions and 17 deletions

View file

@ -25,9 +25,11 @@ class VersionController extends ApplicationApiController
}
/**
* ?
* Returns version information.
*
* @return \Illuminate\Http\JsonResponse
*/
public function __invoke()
public function __invoke(): JsonResponse
{
return new JsonResponse($this->softwareVersionService->getVersionData());
}

View file

@ -168,9 +168,13 @@ class User extends Model implements
*
* @return array
*/
public function toVueObject(): array
public function toReactObject(): array
{
return (new Collection($this->toArray()))->except(['id', 'external_id'])->toArray();
$object = (new Collection($this->toArray()))->except(['id', 'external_id'])->toArray();
$object['avatar_url'] = $this->avatarURL();
$object['role_name'] = $this->roleName();
return $object;
}
/**
@ -203,6 +207,26 @@ class User extends Model implements
return trim($this->name_first . ' ' . $this->name_last);
}
/**
* Get's the avatar url for the user.
*
* @return string
*/
public function avatarURL(): string
{
return 'https://www.gravatar.com/avatar/' . md5($this->email) . '.jpg';
}
/**
* Get's the name of the role assigned to a user.
*
* @return string|null
*/
public function roleName():? string
{
return $this->root_admin ? 'Super Administrator' : null;
}
/**
* Returns all servers that a user owns.
*

View file

@ -20,12 +20,12 @@ abstract class BaseTransformer extends TransformerAbstract
/**
* @var \Pterodactyl\Models\ApiKey
*/
private $key;
private ApiKey $key;
/**
* @var bool
*/
private $rootAdmin;
private bool $rootAdmin;
/**
* Return the resource name for the JSONAPI output.

View file

@ -43,11 +43,10 @@ class UserTransformer extends BaseTransformer
'language' => $model->language,
'root_admin' => (bool) $model->root_admin,
'2fa' => (bool) $model->use_totp,
'avatar_url' => 'https://www.gravatar.com/avatar/' . md5($model->email) . '.jpg?s=40',
'role_name' => $model->root_admin ? 'Super Administrator' : null,
'avatar_url' => $model->avatarURL(),
'role_name' => $model->roleName(),
'created_at' => $this->formatTimestamp($model->created_at),
'updated_at' => $this->formatTimestamp($model->updated_at),
'avatar'
];
}

View file

@ -24,9 +24,13 @@ interface ExtendedWindow extends Window {
username: string;
email: string;
/* eslint-disable camelcase */
name_first: string;
name_last: string;
root_admin: boolean;
use_totp: boolean;
language: string;
avatar_url: string;
role_name: string;
updated_at: string;
created_at: string;
/* eslint-enable camelcase */
@ -52,9 +56,13 @@ const App = () => {
uuid: PterodactylUser.uuid,
username: PterodactylUser.username,
email: PterodactylUser.email,
firstName: PterodactylUser.name_first,
lastName: PterodactylUser.name_last,
language: PterodactylUser.language,
rootAdmin: PterodactylUser.root_admin,
useTotp: PterodactylUser.use_totp,
avatarURL: PterodactylUser.avatar_url,
roleName: PterodactylUser.role_name,
createdAt: new Date(PterodactylUser.created_at),
updatedAt: new Date(PterodactylUser.updated_at),
});

View file

@ -117,7 +117,7 @@ const UsersContainer = () => {
<NavLink to={`${match.url}/${user.id}`}>
<div css={tw`flex items-center`}>
<div css={tw`flex-shrink-0 h-10 w-10`}>
<img css={tw`h-10 w-10 rounded-full`} alt="" src={user.avatarURL}/>
<img css={tw`h-10 w-10 rounded-full`} alt="" src={user.avatarURL + '?s=40'}/>
</div>
<div css={tw`ml-4`}>

View file

@ -1,6 +1,6 @@
import React, { useState } from 'react';
import { NavLink, Route, RouteComponentProps, Switch } from 'react-router-dom';
import { useStoreState } from 'easy-peasy';
import { State, useStoreState } from 'easy-peasy';
import tw from 'twin.macro';
import styled from 'styled-components/macro';
import { ApplicationStore } from '@/state';
@ -88,7 +88,9 @@ const Sidebar = styled.div<{ collapsed?: boolean }>`
`;
const AdminRouter = ({ location, match }: RouteComponentProps) => {
const name = useStoreState((state: ApplicationStore) => state.settings.data!.name);
const user = useStoreState((state: State<ApplicationStore>) => state.user.data);
const applicationName = useStoreState((state: ApplicationStore) => state.settings.data!.name);
const [ collapsed, setCollapsed ] = useState<boolean>();
return (
@ -96,7 +98,7 @@ const AdminRouter = ({ location, match }: RouteComponentProps) => {
<Sidebar collapsed={collapsed}>
<div className={'header'} onClick={ () => { setCollapsed(!collapsed); } }>
{ !collapsed ?
<h1 css={tw`text-2xl text-neutral-50 whitespace-nowrap`}>{name}</h1>
<h1 css={tw`text-2xl text-neutral-50 whitespace-nowrap`}>{applicationName}</h1>
:
<img src={'/favicons/android-icon-48x48.png'} alt={'Pterodactyl Icon'} />
}
@ -162,11 +164,11 @@ const AdminRouter = ({ location, match }: RouteComponentProps) => {
</NavLink>
<div className={'user'}>
<img src={'https://www.gravatar.com/avatar/78a6a270ec41715a8ae96c02b8961f9e?s=64'} alt="Profile Picture" css={tw`h-10 w-10 rounded-full select-none`} />
<img src={user !== undefined ? user.avatarURL + '?s=64' : ''} alt="Profile Picture" css={tw`h-10 w-10 rounded-full select-none`} />
<div css={tw`flex flex-col ml-4`}>
<span css={tw`font-header font-medium text-sm text-neutral-50 whitespace-nowrap leading-tight select-none`}>Matthew Penner</span>
<span css={tw`font-header font-normal text-xs text-neutral-300 whitespace-nowrap leading-tight select-none`}>Super Administrator</span>
<span css={tw`font-header font-medium text-sm text-neutral-50 whitespace-nowrap leading-tight select-none`}>{user?.firstName} {user?.lastName}</span>
<span css={tw`font-header font-normal text-xs text-neutral-300 whitespace-nowrap leading-tight select-none`}>{user?.roleName}</span>
</div>
<NavLink to={'/auth/logout'} css={tw`h-8 w-8 flex items-center justify-center text-neutral-300 hover:text-red-400 hover:bg-neutral-800 rounded ml-auto transition-all duration-100`}>

View file

@ -5,9 +5,13 @@ export interface UserData {
uuid: string;
username: string;
email: string;
firstName: string;
lastName: string;
language: string;
rootAdmin: boolean;
useTotp: boolean;
avatarURL: string;
roleName: string;
createdAt: Date;
updatedAt: Date;
}
@ -21,6 +25,7 @@ export interface UserStore {
const user: UserStore = {
data: undefined,
setUserData: action((state, payload) => {
state.data = payload;
}),

View file

@ -21,7 +21,7 @@
@section('user-data')
@if(!is_null(Auth::user()))
<script>
window.PterodactylUser = {!! json_encode(Auth::user()->toVueObject()) !!};
window.PterodactylUser = {!! json_encode(Auth::user()->toReactObject()) !!};
</script>
@endif
@if(!empty($siteConfiguration))