Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@

namespace Pterodactyl\BlueprintFramework\Libraries\ExtensionLibrary;

use Illuminate\Support\Facades\DB;
use Illuminate\Support\Collection;
use Symfony\Component\Yaml\Yaml;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Pterodactyl\Models\ExtensionCachedMetadata;

class BlueprintBaseLibrary
{
Expand Down Expand Up @@ -294,6 +295,39 @@ public function extensionConfig(string $identifier): ?array
return $conf;
}

/**
* Retrieves the stored metadata for an extension.
*
* This method checks if the given extension has available metadata and, if so,
* returns the metadata. Please note that this metadata may be delayed, and the
* format of which may change, so parse wisely.
*
* Requires the 'remote_metadata' flag to be set to true in Blueprint's settings.
*
* @param string $identifier Extension identifier to retrieve metadata for
*
* @return array|null The metadata array for the extension, or null if not available.
*/
public function extensionMetadata(string $identifier): ?array
{
$metadata = [];
if (!$this->extension($identifier)) {
return null;
}
if($this->dbGet('blueprint', 'flags:remote_metadata')) {
$metadata = ExtensionCachedMetadata::whereIn('identifier', $this->extensions())
->get()
->keyBy('identifier')
->map(fn($m) => $m->metadata)
->toArray();

if(isset($metadata[$identifier])) {
return $metadata[$identifier];
}
}
return null;
}

/**
* Returns a Collection containing all installed extensions's configs.
*
Expand Down
89 changes: 89 additions & 0 deletions app/Console/Commands/BlueprintFramework/MetadataCacheCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<?php

// This command fetches extension metadata on a schedule, to then provide administrators
// with stuff like latest version info, and maybe more in the future.
//
// 1. make a request to blueprint.zip/api/extensions/latest
// 2. figure out which extensions it should write metadata for
// 3. build a json object for those extensions' metadata
// 4. flush metadata table
// 5. write metadata table

namespace Pterodactyl\Console\Commands\BlueprintFramework;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Pterodactyl\Models\ExtensionCachedMetadata;
use Pterodactyl\BlueprintFramework\Services\PlaceholderService\BlueprintPlaceholderService;
use Pterodactyl\BlueprintFramework\Libraries\ExtensionLibrary\Console\BlueprintConsoleLibrary as BlueprintExtensionLibrary;

class MetadataCacheCommand extends Command
{
protected $description = 'Refreshes extension metadata';
protected $signature = 'bp:meta';

public function __construct(
private BlueprintPlaceholderService $PlaceholderService,
private BlueprintExtensionLibrary $blueprint,
) {
parent::__construct();
}

public function handle()
{
if(! $this->blueprint->dbGet("blueprint", "flags:remote_metadata")) {
$this->error('remote_metadata flag set to false');
return false;
}

$now = now();
$rows = [];
$installedExtensions = $this->blueprint->extensions();

// get version info
$context = stream_context_create(['http' => ['method' => 'GET', 'header' => 'User-Agent: BlueprintFramework']]);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

version in the user agent could be useful

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I somewhat agree, but would that kind of conflict with users disabling telemetry?

$remoteVersions = @file_get_contents(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe use a proper http client instead

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I took the file_get_contents shenanigans directly from a few other Artisan commands of ours, but I'll migrate it over to use Illuminate\Support\Facades\Http.

$this->PlaceholderService->api_url() . '/api/extensions/latest',
false,
$context
);

if($remoteVersions) {
$remoteVersionsData = json_decode($remoteVersions, true);
}

if(! isset($remoteVersionsData)) {
$this->error('failed to fetch extension versions');
return false;
}

foreach ($installedExtensions as $identifier) {
if(! isset($remoteVersionsData[$identifier]) || ! is_scalar($remoteVersionsData[$identifier])) continue;

$local_extension = $this->blueprint->extensionConfig($identifier);

$rows[] = [
'identifier' => $identifier,
'metadata' => json_encode([
'latest_version' => (string) $remoteVersionsData[$identifier],
'local_version' => (string) $local_extension['info']['version'] ?? '',
]),
'fetched_at' => $now,
];
}

if (empty($rows)) {
$this->info('no relevant data available, do you have any extensions installed?');
return false;
}

$table = (new ExtensionCachedMetadata())->getTable();

DB::transaction(function () use ($rows, $table) {
DB::table($table)->delete();
DB::table($table)->insert($rows);
});

$this->info('updated extension cached metadata');
}
}
8 changes: 7 additions & 1 deletion app/Console/Kernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ protected function schedule(Schedule $schedule): void
$this->registerTelemetry($schedule);
}

// ============================
// BLUEPRINT SCHEDULES
// ============================

// Blueprint telemetry
$blueprint = app()->make(BlueprintExtensionLibrary::class);
if ($blueprint->dbGet('blueprint', 'flags:telemetry_enabled', 0)) {
Expand All @@ -62,7 +66,9 @@ protected function schedule(Schedule $schedule): void
}

// Blueprint-related utilities
$schedule->command('bp:version:cache')->dailyAt(str_pad(rand(0, 23), 2, '0', STR_PAD_LEFT) . ':' . str_pad(rand(0, 59), 2, '0', STR_PAD_LEFT));
$randTime = str_pad(rand(0, 23), 2, '0', STR_PAD_LEFT) . ':' . str_pad(rand(0, 59), 2, '0', STR_PAD_LEFT);
$schedule->command('bp:version:cache')->dailyAt($randTime);
$schedule->command('bp:meta')->dailyAt($randTime);

// Blueprint extension schedules
GetExtensionSchedules::schedules($schedule);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

namespace Pterodactyl\Http\Controllers\Admin\Extensions\Blueprint;

use Pterodactyl\Http\Controllers\Controller;
use Pterodactyl\Contracts\Repository\SettingsRepositoryInterface;
use Illuminate\Http\RedirectResponse;
use Pterodactyl\Http\Requests\Admin\AdminFormRequest;
use Database\Seeders\BlueprintSeeder;
use Illuminate\Support\Facades\Artisan;
use Pterodactyl\Http\Controllers\Controller;
use Pterodactyl\Http\Requests\Admin\AdminFormRequest;
use Pterodactyl\Contracts\Repository\SettingsRepositoryInterface;

// FIXME: Move form request and remove controller.
class BlueprintExtensionController extends Controller
{

Expand All @@ -26,10 +26,17 @@ public function __construct(
*/
public function update(BlueprintAdminFormRequest $request): RedirectResponse
{
$meta_flag = $this->settings->get('blueprint::flags:remote_metadata');

foreach ($request->validated() as $key => $value) {
$this->settings->set('blueprint::' . $key, $value);
}

// refresh meta if the flag has been altered
if($meta_flag != $request->validated()['flags:remote_metadata']) {
Artisan::call('bp:meta');
}

return redirect()->route('admin.extensions');
}
}
Expand All @@ -41,11 +48,11 @@ public function rules(): array
// Get schema to determine types
$seeder = app(BlueprintSeeder::class);
$schema = $seeder->getSchema();

$rules = [];
foreach ($schema['flags'] as $key => $config) {
$flagPath = "flags:{$key}";

// Build validation rules based on type
switch ($config['type']) {
case 'boolean':
Expand All @@ -62,7 +69,7 @@ public function rules(): array
break;
}
}

return $rules;
}
}
}
19 changes: 15 additions & 4 deletions app/Http/Controllers/Admin/ExtensionsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@

namespace Pterodactyl\Http\Controllers\Admin;

use Illuminate\Support\Facades\Artisan;
use Illuminate\View\View;
use Database\Seeders\BlueprintSeeder;
use Illuminate\Support\Facades\Artisan;
use Illuminate\View\Factory as ViewFactory;
use Pterodactyl\Http\Controllers\Controller;
use Pterodactyl\Models\ExtensionCachedMetadata;
use Pterodactyl\Services\Helpers\SoftwareVersionService;
use Pterodactyl\BlueprintFramework\Services\PlaceholderService\BlueprintPlaceholderService;
use Pterodactyl\BlueprintFramework\Libraries\ExtensionLibrary\Admin\BlueprintAdminLibrary as BlueprintExtensionLibrary;
use Database\Seeders\BlueprintSeeder;

class ExtensionsController extends Controller
{
Expand All @@ -32,7 +33,7 @@ public function index(): View
{
$configuration = $this->blueprint->dbGetMany('blueprint');
$defaults = [];

if (($configuration['internal:version:latest'] ?? false) === false) {
Artisan::call('bp:version:cache');
$latestBlueprintVersion = $this->blueprint->dbGet('blueprint', 'internal:version:latest');
Expand All @@ -47,14 +48,24 @@ public function index(): View
}
}

$metadata = [];
if($this->blueprint->dbGet('blueprint', 'flags:remote_metadata')) {
$metadata = ExtensionCachedMetadata::whereIn('identifier', $this->blueprint->extensions())
->get()
->keyBy('identifier')
->map(fn($m) => $m->metadata)
->toArray();
}

return $this->view->make('admin.extensions', [
'blueprint' => $this->blueprint,
'PlaceholderService' => $this->PlaceholderService,
'configuration' => $configuration,
'latestBlueprintVersion' => $latestBlueprintVersion,
'defaults' => $defaults,
'seeder' => $this->seeder,

'metadata' => $metadata,

'version' => $this->version,
'root' => "/admin/extensions",
]);
Expand Down
26 changes: 26 additions & 0 deletions app/Models/ExtensionCachedMetadata.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace Pterodactyl\Models;

class ExtensionCachedMetadata extends Model
{
protected $table = 'extension_cached_metadata';
protected $casts = [
'metadata' => 'array',
'fetched_at' => 'datetime',
];
protected $fillable = ['identifier', 'metadata', 'fetched_at'];
public $timestamps = true;

// return the latest_version for a given extension identifier, or null if not found
public static function latestVersionFor(string $identifier): ?string
{
$row = static::where('identifier', $identifier)->first(['metadata']);

if (! $row) {
return null;
}

return $row->metadata['latest_version'] ?? null;
}
}
32 changes: 24 additions & 8 deletions blueprint/extensions/blueprint/assets/blueprint.style.css
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@

tag {
display:inline-block;
padding:3px;
background-color:#505050;
border-radius:5px;
padding: 3px 5px;
background-color: #4d5b69;
border-radius:15px;
font-size:12px;
color:white;
}
Expand All @@ -36,7 +36,7 @@ tag[mg-right] {margin-right:5px;}
tag[red] {background-color:#ff4040;}
tag[green] {background-color:#27b949;}
tag[blue] {background-color:#288afb;}
[ext-title]{display:flex; flex-direction:row; align-items:center;}
[ext-title]{display:flex; flex-direction:row; align-items: end;}


.btn-gray {
Expand Down Expand Up @@ -111,7 +111,18 @@ tag[blue] {background-color:#288afb;}
width: calc( 100% - 15px );
overflow: clip;
text-overflow: ellipsis;
opacity: .6;
}
.extension-btn-update {
background-color: #194323;
color: #3dd15f !important;
border: 1px solid #265f33;
font-weight: 700;
border-radius: 12px;
padding: 0 8px;
display: inline-flex;
flex-direction: row;
gap: 5px;
margin-left: 5px;
}
.extension-btn {
background-color:#1f2933;
Expand All @@ -120,7 +131,6 @@ tag[blue] {background-color:#288afb;}
height:calc(65px + 14px);
padding: 0px !important;
overflow:hidden;
vertical-align:center;
transition:background-color .2s;
border-radius:8px;
}
Expand Down Expand Up @@ -169,9 +179,15 @@ tag[blue] {background-color:#288afb;}
margin-left: -7px;
border-width: 7px;
border-style: solid;
border-color:
border-color:
transparent
transparent
#1f2933
transparent;
}
}

@media screen and (width <= 600px) {
.blueprint-extension-title-tag-icon {
display: none;
}
}
5 changes: 5 additions & 0 deletions database/Seeders/BlueprintSeeder.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ class BlueprintSeeder extends Seeder
'type' => 'boolean',
'hidden' => false,
],
'remote_metadata' => [
'default' => true,
'type' => 'boolean',
'hidden' => false,
],
'show_in_sidebar' => [
'default' => false,
'type' => 'boolean',
Expand Down
Loading
Loading