forked from Alex/Pterodactyl-Panel
Include egg variables in the output from the API
This commit is contained in:
parent
3a2c60ce31
commit
cae604e79d
@ -2,6 +2,27 @@
|
||||
|
||||
namespace Pterodactyl\Models;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property int $egg_id
|
||||
* @property string $name
|
||||
* @property string $description
|
||||
* @property string $env_variable
|
||||
* @property string $default_value
|
||||
* @property bool $user_viewable
|
||||
* @property bool $user_editable
|
||||
* @property string $rules
|
||||
* @property \Carbon\CarbonImmutable $created_at
|
||||
* @property \Carbon\CarbonImmutable $updated_at
|
||||
*
|
||||
* @property bool $required
|
||||
* @property \Pterodactyl\Models\Egg $egg
|
||||
* @property \Pterodactyl\Models\ServerVariable $serverVariable
|
||||
*
|
||||
* The "server_value" variable is only present on the object if you've loaded this model
|
||||
* using the server relationship.
|
||||
* @property string|null $server_value
|
||||
*/
|
||||
class EggVariable extends Model
|
||||
{
|
||||
/**
|
||||
@ -17,6 +38,11 @@ class EggVariable extends Model
|
||||
*/
|
||||
const RESERVED_ENV_NAMES = 'SERVER_MEMORY,SERVER_IP,SERVER_PORT,ENV,HOME,USER,STARTUP,SERVER_UUID,UUID';
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $immutableDates = true;
|
||||
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
@ -38,8 +64,8 @@ class EggVariable extends Model
|
||||
*/
|
||||
protected $casts = [
|
||||
'egg_id' => 'integer',
|
||||
'user_viewable' => 'integer',
|
||||
'user_editable' => 'integer',
|
||||
'user_viewable' => 'bool',
|
||||
'user_editable' => 'bool',
|
||||
];
|
||||
|
||||
/**
|
||||
@ -65,12 +91,19 @@ class EggVariable extends Model
|
||||
];
|
||||
|
||||
/**
|
||||
* @param $value
|
||||
* @return bool
|
||||
*/
|
||||
public function getRequiredAttribute($value)
|
||||
public function getRequiredAttribute()
|
||||
{
|
||||
return $this->rules === 'required' || str_contains($this->rules, ['required|', '|required']);
|
||||
return in_array('required', explode('|', $this->rules));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasOne
|
||||
*/
|
||||
public function egg()
|
||||
{
|
||||
return $this->hasOne(Egg::class);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -45,7 +45,7 @@ use Znck\Eloquent\Traits\BelongsToThrough;
|
||||
* @property \Pterodactyl\Models\Node $node
|
||||
* @property \Pterodactyl\Models\Nest $nest
|
||||
* @property \Pterodactyl\Models\Egg $egg
|
||||
* @property \Pterodactyl\Models\ServerVariable[]|\Illuminate\Database\Eloquent\Collection $variables
|
||||
* @property \Pterodactyl\Models\EggVariable[]|\Illuminate\Database\Eloquent\Collection $variables
|
||||
* @property \Pterodactyl\Models\Schedule[]|\Illuminate\Database\Eloquent\Collection $schedule
|
||||
* @property \Pterodactyl\Models\Database[]|\Illuminate\Database\Eloquent\Collection $databases
|
||||
* @property \Pterodactyl\Models\Location $location
|
||||
@ -270,7 +270,9 @@ class Server extends Model
|
||||
*/
|
||||
public function variables()
|
||||
{
|
||||
return $this->hasMany(ServerVariable::class);
|
||||
return $this->hasMany(EggVariable::class, 'egg_id', 'egg_id')
|
||||
->select(['egg_variables.*', 'server_variables.variable_value as server_value'])
|
||||
->leftJoin('server_variables', 'server_variables.variable_id', '=', 'egg_variables.id');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -143,6 +143,10 @@ class ServerRepository extends EloquentRepository implements ServerRepositoryInt
|
||||
*/
|
||||
public function getVariablesWithValues(int $id, bool $returnAsObject = false)
|
||||
{
|
||||
$this->getBuilder()
|
||||
->with('variables', 'egg.variables')
|
||||
->findOrFail($id);
|
||||
|
||||
try {
|
||||
$instance = $this->getBuilder()->with('variables', 'egg.variables')->find($id, $this->getColumns());
|
||||
} catch (ModelNotFoundException $exception) {
|
||||
|
27
app/Services/Servers/StartupCommandService.php
Normal file
27
app/Services/Servers/StartupCommandService.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Pterodactyl\Services\Servers;
|
||||
|
||||
use Pterodactyl\Models\Server;
|
||||
|
||||
class StartupCommandService
|
||||
{
|
||||
/**
|
||||
* Generates a startup command for a given server instance.
|
||||
*
|
||||
* @param \Pterodactyl\Models\Server $server
|
||||
* @return string
|
||||
*/
|
||||
public function handle(Server $server): string
|
||||
{
|
||||
$find = ['{{SERVER_MEMORY}}', '{{SERVER_IP}}', '{{SERVER_PORT}}'];
|
||||
$replace = [$server->memory, $server->allocation->ip, $server->allocation->port];
|
||||
|
||||
foreach ($server->variables as $variable) {
|
||||
$find[] = '{{' . $variable->env_variable . '}}';
|
||||
$replace[] = $variable->user_viewable ? ($variable->server_value ?? $variable->default_value) : '[hidden]';
|
||||
}
|
||||
|
||||
return str_replace($find, $replace, $server->startup);
|
||||
}
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Pterodactyl\Services\Servers;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
|
||||
|
||||
class StartupCommandViewService
|
||||
{
|
||||
/**
|
||||
* @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface
|
||||
*/
|
||||
private $repository;
|
||||
|
||||
/**
|
||||
* StartupCommandViewService constructor.
|
||||
*
|
||||
* @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository
|
||||
*/
|
||||
public function __construct(ServerRepositoryInterface $repository)
|
||||
{
|
||||
$this->repository = $repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a startup command for a server and return all of the user-viewable variables
|
||||
* as well as their assigned values.
|
||||
*
|
||||
* @param int $server
|
||||
* @return \Illuminate\Support\Collection
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||
*/
|
||||
public function handle(int $server): Collection
|
||||
{
|
||||
$response = $this->repository->getVariablesWithValues($server, true);
|
||||
$server = $this->repository->getPrimaryAllocation($response->server);
|
||||
|
||||
$find = ['{{SERVER_MEMORY}}', '{{SERVER_IP}}', '{{SERVER_PORT}}'];
|
||||
$replace = [$server->memory, $server->getRelation('allocation')->ip, $server->getRelation('allocation')->port];
|
||||
|
||||
$variables = $server->getRelation('egg')->getRelation('variables')
|
||||
->each(function ($variable) use (&$find, &$replace, $response) {
|
||||
$find[] = '{{' . $variable->env_variable . '}}';
|
||||
$replace[] = $variable->user_viewable ? $response->data[$variable->env_variable] : '[hidden]';
|
||||
})->filter(function ($variable) {
|
||||
return $variable->user_viewable === 1;
|
||||
});
|
||||
|
||||
return collect([
|
||||
'startup' => str_replace($find, $replace, $server->startup),
|
||||
'variables' => $variables,
|
||||
'server_values' => $response->data,
|
||||
]);
|
||||
}
|
||||
}
|
33
app/Transformers/Api/Client/EggVariableTransformer.php
Normal file
33
app/Transformers/Api/Client/EggVariableTransformer.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace Pterodactyl\Transformers\Api\Client;
|
||||
|
||||
use Pterodactyl\Models\EggVariable;
|
||||
|
||||
class EggVariableTransformer extends BaseClientTransformer
|
||||
{
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getResourceName(): string
|
||||
{
|
||||
return EggVariable::RESOURCE_NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Pterodactyl\Models\EggVariable $variable
|
||||
* @return array
|
||||
*/
|
||||
public function transform(EggVariable $variable)
|
||||
{
|
||||
return [
|
||||
'name' => $variable->name,
|
||||
'description' => $variable->description,
|
||||
'env_variable' => $variable->env_variable,
|
||||
'default_value' => $variable->default_value,
|
||||
'server_value' => $variable->server_value,
|
||||
'is_editable' => $variable->user_editable,
|
||||
'rules' => $variable->rules,
|
||||
];
|
||||
}
|
||||
}
|
@ -6,13 +6,17 @@ use Pterodactyl\Models\Egg;
|
||||
use Pterodactyl\Models\Server;
|
||||
use Pterodactyl\Models\Subuser;
|
||||
use Pterodactyl\Models\Allocation;
|
||||
use Illuminate\Container\Container;
|
||||
use Pterodactyl\Models\EggVariable;
|
||||
use Pterodactyl\Services\Servers\StartupCommandService;
|
||||
use Pterodactyl\Transformers\Api\Client\EggVariableTransformer;
|
||||
|
||||
class ServerTransformer extends BaseClientTransformer
|
||||
{
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $defaultIncludes = ['allocations'];
|
||||
protected $defaultIncludes = ['allocations', 'variables'];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
@ -36,6 +40,9 @@ class ServerTransformer extends BaseClientTransformer
|
||||
*/
|
||||
public function transform(Server $server): array
|
||||
{
|
||||
/** @var \Pterodactyl\Services\Servers\StartupCommandService $service */
|
||||
$service = Container::getInstance()->make(StartupCommandService::class);
|
||||
|
||||
return [
|
||||
'server_owner' => $this->getKey()->user_id === $server->owner_id,
|
||||
'identifier' => $server->uuidShort,
|
||||
@ -54,6 +61,7 @@ class ServerTransformer extends BaseClientTransformer
|
||||
'io' => $server->io,
|
||||
'cpu' => $server->cpu,
|
||||
],
|
||||
'invocation' => $service->handle($server),
|
||||
'feature_limits' => [
|
||||
'databases' => $server->database_limit,
|
||||
'allocations' => $server->allocation_limit,
|
||||
@ -80,6 +88,20 @@ class ServerTransformer extends BaseClientTransformer
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Pterodactyl\Models\Server $server
|
||||
* @return \League\Fractal\Resource\Collection
|
||||
* @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException
|
||||
*/
|
||||
public function includeVariables(Server $server)
|
||||
{
|
||||
return $this->collection(
|
||||
$server->variables->where('user_viewable', true),
|
||||
$this->makeTransformer(EggVariableTransformer::class),
|
||||
EggVariable::RESOURCE_NAME
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the egg associated with this server.
|
||||
*
|
||||
|
@ -19,6 +19,7 @@ export interface Server {
|
||||
ip: string;
|
||||
port: number;
|
||||
};
|
||||
invocation: string;
|
||||
description: string;
|
||||
allocations: Allocation[];
|
||||
limits: {
|
||||
@ -43,6 +44,7 @@ export const rawDataToServerObject = ({ attributes: data }: FractalResponseData)
|
||||
uuid: data.uuid,
|
||||
name: data.name,
|
||||
node: data.node,
|
||||
invocation: data.invocation,
|
||||
sftpDetails: {
|
||||
ip: data.sftp_details.ip,
|
||||
port: data.sftp_details.port,
|
||||
|
@ -3,10 +3,26 @@ import ContentContainer from '@/components/elements/ContentContainer';
|
||||
import { CSSTransition } from 'react-transition-group';
|
||||
import tw from 'twin.macro';
|
||||
import FlashMessageRender from '@/components/FlashMessageRender';
|
||||
import { Helmet } from 'react-helmet';
|
||||
import useServer from '@/plugins/useServer';
|
||||
|
||||
const PageContentBlock: React.FC<{ showFlashKey?: string; className?: string }> = ({ children, showFlashKey, className }) => (
|
||||
interface Props {
|
||||
title?: string;
|
||||
className?: string;
|
||||
showFlashKey?: string;
|
||||
}
|
||||
|
||||
const PageContentBlock: React.FC<Props> = ({ title, showFlashKey, className, children }) => {
|
||||
const { name } = useServer();
|
||||
|
||||
return (
|
||||
<CSSTransition timeout={150} classNames={'fade'} appear in>
|
||||
<>
|
||||
{!!title &&
|
||||
<Helmet>
|
||||
<title>{name} | {title}</title>
|
||||
</Helmet>
|
||||
}
|
||||
<ContentContainer css={tw`my-10`} className={className}>
|
||||
{showFlashKey &&
|
||||
<FlashMessageRender byKey={showFlashKey} css={tw`mb-4`}/>
|
||||
@ -28,6 +44,7 @@ const PageContentBlock: React.FC<{ showFlashKey?: string; className?: string }>
|
||||
</ContentContainer>
|
||||
</>
|
||||
</CSSTransition>
|
||||
);
|
||||
);
|
||||
};
|
||||
|
||||
export default PageContentBlock;
|
||||
|
@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
import PageContentBlock from '@/components/elements/PageContentBlock';
|
||||
import TitledGreyBox from '@/components/elements/TitledGreyBox';
|
||||
import useServer from '@/plugins/useServer';
|
||||
import tw from 'twin.macro';
|
||||
|
||||
const StartupContainer = () => {
|
||||
const { invocation } = useServer();
|
||||
|
||||
return (
|
||||
<PageContentBlock title={'Startup Settings'} showFlashKey={'server:startup'}>
|
||||
<TitledGreyBox title={'Startup Command'}>
|
||||
<div css={tw`px-1 py-2`}>
|
||||
<p css={tw`font-mono bg-neutral-900 rounded py-2 px-4`}>
|
||||
{invocation}
|
||||
</p>
|
||||
</div>
|
||||
</TitledGreyBox>
|
||||
</PageContentBlock>
|
||||
);
|
||||
};
|
||||
|
||||
export default StartupContainer;
|
@ -27,6 +27,7 @@ import ScreenBlock from '@/components/screens/ScreenBlock';
|
||||
import SubNavigation from '@/components/elements/SubNavigation';
|
||||
import NetworkContainer from '@/components/server/network/NetworkContainer';
|
||||
import InstallListener from '@/components/server/InstallListener';
|
||||
import StartupContainer from '@/components/server/startup/StartupContainer';
|
||||
|
||||
const ServerRouter = ({ match, location }: RouteComponentProps<{ id: string }>) => {
|
||||
const { rootAdmin } = useStoreState(state => state.user.data!);
|
||||
@ -98,6 +99,9 @@ const ServerRouter = ({ match, location }: RouteComponentProps<{ id: string }>)
|
||||
<Can action={'allocations.*'}>
|
||||
<NavLink to={`${match.url}/network`}>Network</NavLink>
|
||||
</Can>
|
||||
<Can action={'startup.*'}>
|
||||
<NavLink to={`${match.url}/startup`}>Startup</NavLink>
|
||||
</Can>
|
||||
<Can action={[ 'settings.*', 'file.sftp' ]} matchAny>
|
||||
<NavLink to={`${match.url}/settings`}>Settings</NavLink>
|
||||
</Can>
|
||||
@ -137,6 +141,7 @@ const ServerRouter = ({ match, location }: RouteComponentProps<{ id: string }>)
|
||||
<Route path={`${match.path}/users`} component={UsersContainer} exact/>
|
||||
<Route path={`${match.path}/backups`} component={BackupContainer} exact/>
|
||||
<Route path={`${match.path}/network`} component={NetworkContainer} exact/>
|
||||
<Route path={`${match.path}/startup`} component={StartupContainer} exact/>
|
||||
<Route path={`${match.path}/settings`} component={SettingsContainer} exact/>
|
||||
<Route path={'*'} component={NotFound}/>
|
||||
</Switch>
|
||||
|
@ -9,7 +9,7 @@ use Pterodactyl\Models\Server;
|
||||
use Illuminate\Support\Collection;
|
||||
use Pterodactyl\Models\Allocation;
|
||||
use Pterodactyl\Models\EggVariable;
|
||||
use Pterodactyl\Services\Servers\StartupCommandViewService;
|
||||
use Pterodactyl\Services\Servers\StartupCommandService;
|
||||
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
|
||||
|
||||
class StartupCommandViewServiceTest extends TestCase
|
||||
@ -76,10 +76,10 @@ class StartupCommandViewServiceTest extends TestCase
|
||||
/**
|
||||
* Return an instance of the service with mocked dependencies.
|
||||
*
|
||||
* @return \Pterodactyl\Services\Servers\StartupCommandViewService
|
||||
* @return \Pterodactyl\Services\Servers\StartupCommandService
|
||||
*/
|
||||
private function getService(): StartupCommandViewService
|
||||
private function getService(): StartupCommandService
|
||||
{
|
||||
return new StartupCommandViewService($this->repository);
|
||||
return new StartupCommandService($this->repository);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user