Skip to content
Merged
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ yarn-error.log
/.vscode
/.zed
amazeeai-trial.pass
/*.sql
16 changes: 7 additions & 9 deletions .lagoon.env
Original file line number Diff line number Diff line change
Expand Up @@ -37,18 +37,16 @@ QUEUE_CONNECTION=redis
# No environment vars

# Freedom Tech PHP Lagoon Client Settings
FTLAGOON_PRIVATE_KEY_FILE=storage/ftlagoon/.ssh/id_rsa
FTLAGOON_TOKEN_CACHE_DIR=storage/ftlagoon/.tokencache/
FTLAGOON_PRIVATE_KEY_FILE=/app/storage/ftlagoon/.ssh/polydock
FTLAGOON_TOKEN_CACHE_DIR=/app/storage/ftlagoon/.tokencache/

FTLAGOON_ENDPOINT=https://api.main.lagoon-core.test6.amazee.io/graphql
FTLAGOON_SSH_USER=lagoon
FTLAGOON_SSH_PORT=22
FTLAGOON_SSH_SERVER=ssh.main.lagoon-core.test6.amazee.io
FTLAGOON_SSH_PRIVATE_KEY_FILE=/home/bryan/.ssh/id_rsa
# FTLAGOON_ENDPOINT=https://api.main.lagoon-core.test6.amazee.io/graphql
# FTLAGOON_SSH_USER=lagoon
# FTLAGOON_SSH_PORT=22
# FTLAGOON_SSH_SERVER=ssh.main.lagoon-core.test6.amazee.io

# FTLAGOON_PRIVATE_KEY_FILE=storage/ftlagoon/.ssh/polydock
# FTLAGOON_ENDPOINT=https://api.lagoon.amazeeio.cloud/graphql
# FTLAGOON_SSH_USER=lagoon
# FTLAGOON_SSH_PORT=32222
# FTLAGOON_SSH_SERVER=ssh.lagoon.amazeeio.cloud
# FTLAGOON_SSH_PRIVATE_KEY_FILE=/home/bryan/.ssh/id_rsa

3 changes: 2 additions & 1 deletion app/Console/Commands/CreateStore.php
Original file line number Diff line number Diff line change
Expand Up @@ -108,11 +108,12 @@ public function handle()
'lagoon_deploy_region_id_ext' => $regionId,
'lagoon_deploy_project_prefix' => $prefix,
'lagoon_deploy_organization_id_ext' => $orgId,
'lagoon_deploy_private_key' => $deployKey,
'amazee_ai_backend_region_id_ext' => $aiRegionId,
'lagoon_deploy_group_name' => $groupName,
]);

$store->setPolydockVariableValue('lagoon_deploy_private_key', $deployKey, true);

$this->info("✅ Store '{$store->name}' created successfully with ID: {$store->id}");

return 0;
Expand Down
19 changes: 18 additions & 1 deletion app/Console/Commands/CreateStoreApp.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use App\Enums\PolydockStoreAppStatusEnum;
use App\Models\PolydockStore;
use App\Models\PolydockStoreApp;
use App\Services\PolydockAppClassDiscovery;
use Illuminate\Console\Command;

class CreateStoreApp extends Command
Expand Down Expand Up @@ -72,7 +73,23 @@ public function handle(): int

// Gather app information
$name = $this->option('name') ?? $this->ask('App name');
$appClass = $this->option('app-class') ?? $this->ask('Polydock app class');
$discovery = app(PolydockAppClassDiscovery::class);
$availableClasses = $discovery->getAvailableAppClasses();
$appClass = $this->option('app-class');
if ($appClass) {
if (! $discovery->isValidAppClass($appClass)) {
$this->error("Invalid app class: {$appClass}. Must be a concrete PolydockAppInterface implementation.");

return 1;
}
} else {
if (empty($availableClasses)) {
$this->error('No Polydock app classes found. Ensure packages are installed correctly.');

return 1;
}
$appClass = $this->choice('Select Polydock app class', array_keys($availableClasses));
}
$description = $this->option('description') ?? $this->ask('App description');
$author = $this->option('author') ?? $this->ask('Author');
$website = $this->option('website') ?? $this->ask('Author website');
Expand Down
127 changes: 115 additions & 12 deletions app/Filament/Admin/Resources/PolydockAppInstanceResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use App\Models\PolydockAppInstance;
use App\PolydockEngine\Helpers\AmazeeAiBackendHelper;
use App\PolydockEngine\Helpers\LagoonHelper;
use App\Services\PolydockAppClassDiscovery;
use Filament\Forms;
use Filament\Forms\Form;
use Filament\Infolists\Infolist;
Expand All @@ -17,6 +18,7 @@
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Table;
use FreedomtechHosting\PolydockApp\Attributes\PolydockAppInstanceFields;
use FreedomtechHosting\PolydockApp\Enums\PolydockAppInstanceStatus;

class PolydockAppInstanceResource extends Resource
Expand Down Expand Up @@ -70,20 +72,28 @@ public static function table(Table $table): Table
TextColumn::make('send_midtrial_email_at')
->label('Midtrial Email')
->description(fn ($record) => $record->midtrial_email_sent ? 'Sent' : 'Pending')
->state(fn ($record) => $record->send_midtrial_email_at ? $record->send_midtrial_email_at->format('Y-m-d H:i:s') : ''),
->state(fn ($record) => $record->send_midtrial_email_at
? $record->send_midtrial_email_at->format('Y-m-d H:i:s')
: ''),
TextColumn::make('send_one_day_left_email_at')
->label('1D Left Email')
->description(fn ($record) => $record->one_day_left_email_sent ? 'Sent' : 'Pending')
->state(fn ($record) => $record->send_one_day_left_email_at ? $record->send_one_day_left_email_at->format('Y-m-d H:i:s') : ''),
->state(fn ($record) => $record->send_one_day_left_email_at
? $record->send_one_day_left_email_at->format('Y-m-d H:i:s')
: ''),
TextColumn::make('trial_complete_email_sent')
->label('Trial Complete Email')
->state(fn ($record) => ($record->is_trial && $record->trial_complete_email_sent) ? 'Sent' : ($record->is_trial ? 'Pending' : '')),
->state(fn ($record) => $record->is_trial && $record->trial_complete_email_sent
? 'Sent'
: ($record->is_trial ? 'Pending' : '')),
])
->filters([
SelectFilter::make('status')
->options(collect(PolydockAppInstanceStatus::cases())
->mapWithKeys(fn ($status) => [$status->value => $status->getLabel()])
->toArray())
->options(
collect(PolydockAppInstanceStatus::cases())
->mapWithKeys(fn ($status) => [$status->value => $status->getLabel()])
->toArray(),
)
->multiple()
->label('Instance Status')
->indicator('Status'),
Expand Down Expand Up @@ -165,7 +175,8 @@ public static function table(Table $table): Table
->actions([
Tables\Actions\ViewAction::make(),
Tables\Actions\EditAction::make(),
])->headerActions([
])
->headerActions([
ExportAction::make()
->label('Export registrations')
->exporter(UserRemoteRegistrationExporter::class),
Expand Down Expand Up @@ -205,10 +216,19 @@ public static function infolist(Infolist $infolist): Infolist
->schema([
\Filament\Infolists\Components\TextEntry::make('storeApp.lagoon_deploy_region_id_ext')
->label('Deploy Region')
->formatStateUsing(fn ($state) => LagoonHelper::getLagoonCodeDataValueForRegion($state, 'name')),
\Filament\Infolists\Components\TextEntry::make('storeApp.amazee_ai_backend_region_id_ext')
->formatStateUsing(
fn ($state) => LagoonHelper::getLagoonCodeDataValueForRegion($state, 'name'),
),
\Filament\Infolists\Components\TextEntry::make(
'storeApp.amazee_ai_backend_region_id_ext',
)
->label('AI Backend Region')
->formatStateUsing(fn ($state) => AmazeeAiBackendHelper::getAmazeeAiBackendCodeDataValueForRegion($state, 'name')),
->formatStateUsing(
fn ($state) => AmazeeAiBackendHelper::getAmazeeAiBackendCodeDataValueForRegion(
$state,
'name',
),
),
]),
])
->columnSpan(2),
Expand All @@ -229,6 +249,13 @@ public static function infolist(Infolist $infolist): Infolist
])
->columnSpan(1),

\Filament\Infolists\Components\Section::make('Instance Configuration')
->description('Instance-specific settings configured at creation.')
->schema(fn ($record) => self::getRenderedInstanceConfigForRecord($record))
->visible(fn ($record) => self::hasInstanceConfigFields($record))
->columnSpan(3)
->collapsible(),

\Filament\Infolists\Components\Section::make('Instance Data')
->description('Safe data that can be shared with webhooks')
->schema(fn ($record) => self::getRenderedSafeDataForRecord($record))
Expand All @@ -241,12 +268,17 @@ public static function infolist(Infolist $infolist): Infolist
public static function getRenderedSafeDataForRecord(PolydockAppInstance $record): array
{
$safeData = $record->data;
$sensitiveKeys = $record->getSensitiveDataKeys();
$renderedArray = [];
foreach ($safeData as $key => $value) {
if ($record->shouldFilterKey($key, $record->getSensitiveDataKeys())) {
if ($record->shouldFilterKey($key, $sensitiveKeys)) {
$value = 'REDACTED';
}

if ($value === null) {
$value = 'N/A';
}

$renderKey = 'webhook_data_'.$key;
$renderedItem = \Filament\Infolists\Components\TextEntry::make($renderKey)
->label($key)
Expand All @@ -266,6 +298,76 @@ public static function getRenderedSafeDataForRecord(PolydockAppInstance $record)
return $renderedArray;
}

/**
* Check if the record's app class defines instance configuration fields.
*/
public static function hasInstanceConfigFields(PolydockAppInstance $record): bool
{
$storeApp = $record->storeApp;
if (! $storeApp || empty($storeApp->polydock_app_class)) {
return false;
}

$discovery = app(PolydockAppClassDiscovery::class);

return ! empty($discovery->getAppInstanceInfolistSchema($storeApp->polydock_app_class));
}

/**
* Get rendered infolist components for instance configuration fields.
*
* Values are loaded from PolydockVariables associated with the app instance.
*/
public static function getRenderedInstanceConfigForRecord(PolydockAppInstance $record): array
{
$storeApp = $record->storeApp;
if (! $storeApp || empty($storeApp->polydock_app_class)) {
return [];
}

$discovery = app(PolydockAppClassDiscovery::class);
$fieldNames = $discovery->getAppInstanceFormFieldNames($storeApp->polydock_app_class);

if (empty($fieldNames)) {
return [];
}

// Build a simple display of instance config values from PolydockVariables
$renderedArray = [];
$instanceConfigPrefix = PolydockAppInstanceFields::FIELD_PREFIX;

foreach ($fieldNames as $fieldName) {
$value = $record->getPolydockVariableValue($fieldName);

// Create a human-readable label from the field name
// e.g., "instance_config_ai_model_override" -> "Ai Model Override"
$labelName = str_replace($instanceConfigPrefix, '', $fieldName);
$labelName = str_replace('_', ' ', $labelName);
$labelName = ucwords($labelName);

// Check if value should be masked (for encrypted fields)
$isEncrypted = $record->isPolydockVariableEncrypted($fieldName);

$renderedItem = \Filament\Infolists\Components\TextEntry::make('instance_config_display_'.$fieldName)
->label($labelName);

if ($isEncrypted && $value !== null && $value !== '') {
// Mask encrypted values
$renderedItem->state('********');
} elseif ($value === null || $value === '') {
$renderedItem->state('Not configured')
->color('gray');
} else {
$renderedItem->state($value);
}

$renderedArray[] = $renderedItem;
}

return $renderedArray;
}

#[\Override]
public static function getRelations(): array
{
return [
Expand All @@ -276,14 +378,15 @@ public static function getRelations(): array
#[\Override]
public static function canCreate(): bool
{
return false;
return true;
}

#[\Override]
public static function getPages(): array
{
return [
'index' => Pages\ListPolydockAppInstances::route('/'),
'create' => Pages\CreatePolydockAppInstance::route('/create'),
'view' => Pages\ViewPolydockAppInstance::route('/{record}'),
'edit' => Pages\EditPolydockAppInstance::route('/{record}/edit'),
];
Expand Down
Loading
Loading