Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
84 changes: 84 additions & 0 deletions app/Filament/Admin/Resources/ApiTokenResource.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<?php

declare(strict_types=1);

namespace App\Filament\Admin\Resources;

use App\Filament\Admin\Resources\ApiTokenResource\Pages;
use App\Models\User;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Laravel\Sanctum\PersonalAccessToken;

class ApiTokenResource extends Resource
{
protected static ?string $model = PersonalAccessToken::class;

protected static ?string $navigationIcon = 'heroicon-o-key';

protected static ?string $navigationGroup = 'Users';

protected static ?string $navigationLabel = 'API Tokens';

protected static ?int $navigationSort = 120;

#[\Override]
public static function getEloquentQuery(): Builder
{
return parent::getEloquentQuery()
->where('tokenable_type', User::class);
}

#[\Override]
public static function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('name')
->searchable()
->sortable(),
Tables\Columns\TextColumn::make('tokenable.email')
->label('Owner')
->searchable()
->sortable(),
Tables\Columns\TextColumn::make('abilities')
->formatStateUsing(
static fn (mixed $state): string => is_array($state) ? implode(', ', $state) : (string) $state
)
->label('Abilities')
->wrap(),
Tables\Columns\TextColumn::make('last_used_at')
->dateTime()
->sortable()
->placeholder('Never'),
Tables\Columns\TextColumn::make('expires_at')
->dateTime()
->sortable()
->placeholder('Never'),
Tables\Columns\TextColumn::make('created_at')
->dateTime()
->sortable(),
])
->actions([
Tables\Actions\DeleteAction::make()
->label('Revoke'),
])
->bulkActions([]);
}

#[\Override]
public static function canCreate(): bool
{
return false;
}

#[\Override]
public static function getPages(): array
{
return [
'index' => Pages\ListApiTokens::route('/'),
];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php

declare(strict_types=1);

namespace App\Filament\Admin\Resources\ApiTokenResource\Pages;

use App\Filament\Admin\Resources\ApiTokenResource;
use App\Models\User;
use Filament\Actions\Action;
use Filament\Forms;
use Filament\Notifications\Notification;
use Filament\Resources\Pages\ListRecords;
use Illuminate\Support\Carbon;

class ListApiTokens extends ListRecords
{
protected static string $resource = ApiTokenResource::class;

#[\Override]
protected function getHeaderActions(): array
{
return [
Action::make('createToken')
->label('Create Token')
->icon('heroicon-o-plus')
->modalHeading('Create API token')
->modalDescription('The token will only be shown once after creation.')
->form([
Forms\Components\Select::make('user_id')
->label('Owner user')
->required()
->searchable()
->preload()
->options(User::query()->orderBy('email')->pluck('email', 'id')),
Forms\Components\TextInput::make('token_name')
->label('Token name')
->required()
->maxLength(255),
Forms\Components\CheckboxList::make('abilities')
->label('Abilities')
->options([
'instances.read' => 'instances.read',
'instances.write' => 'instances.write',
'*' => '* (full access)',
])
->default(['instances.read'])
->required()
->minItems(1)
->columns(1),
Forms\Components\DateTimePicker::make('expires_at')
->label('Expires at')
->seconds(false),
])
->action(function (array $data): void {
/** @var User $user */
$user = User::query()->findOrFail($data['user_id']);
$expiresAt = ! empty($data['expires_at']) ? Carbon::parse((string) $data['expires_at']) : null;
$token = $user->createToken(
name: (string) $data['token_name'],
abilities: array_values($data['abilities']),
expiresAt: $expiresAt,
);

Notification::make()
->title('API token created')
->body("Copy this token now. It will not be shown again:\n{$token->plainTextToken}")
->success()
->persistent()
->send();
}),
];
}
}
Loading