mirror of
https://github.com/pelican-dev/panel.git
synced 2025-05-19 22:14:45 +02:00
Allow Database Hosts to have multiple Nodes (#767)
* WIP * Update laravel and migrations * WIP * fix tests * Update composer * Fix transformer * Fix filament pages * WIP * Update DatabaseHostTransformer * fix: tests * pint this files pls * resolve merge better * Update migration * Update Migration, Again * Update down migration --------- Co-authored-by: Vehikl <go@vehikl.com>
This commit is contained in:
parent
5b3ae995e6
commit
7e7f0be7df
@ -78,12 +78,13 @@ class CreateDatabaseHost extends CreateRecord
|
||||
->revealable()
|
||||
->maxLength(255)
|
||||
->required(),
|
||||
Select::make('node_id')
|
||||
Select::make('node_ids')
|
||||
->multiple()
|
||||
->searchable()
|
||||
->preload()
|
||||
->helperText('This setting only defaults to this database host when adding a database to a server on the selected node.')
|
||||
->label('Linked Node')
|
||||
->relationship('node', 'name'),
|
||||
->label('Linked Nodes')
|
||||
->relationship('nodes', 'name'),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
@ -73,12 +73,13 @@ class EditDatabaseHost extends EditRecord
|
||||
->password()
|
||||
->revealable()
|
||||
->maxLength(255),
|
||||
Select::make('node_id')
|
||||
Select::make('nodes')
|
||||
->multiple()
|
||||
->searchable()
|
||||
->preload()
|
||||
->helperText('This setting only defaults to this database host when adding a database to a server on the selected node.')
|
||||
->label('Linked Node')
|
||||
->relationship('node', 'name'),
|
||||
->label('Linked Nodes')
|
||||
->relationship('nodes', 'name'),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
@ -36,8 +36,9 @@ class ListDatabaseHosts extends ListRecords
|
||||
->counts('databases')
|
||||
->icon('tabler-database')
|
||||
->label('Databases'),
|
||||
TextColumn::make('node.name')
|
||||
TextColumn::make('nodes.name')
|
||||
->icon('tabler-server-2')
|
||||
->badge()
|
||||
->placeholder('No Nodes')
|
||||
->sortable(),
|
||||
])
|
||||
|
@ -2,8 +2,8 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
@ -39,7 +39,7 @@ class DatabaseHost extends Model
|
||||
* Fields that are mass assignable.
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name', 'host', 'port', 'username', 'password', 'max_databases', 'node_id',
|
||||
'name', 'host', 'port', 'username', 'password', 'max_databases',
|
||||
];
|
||||
|
||||
/**
|
||||
@ -51,7 +51,8 @@ class DatabaseHost extends Model
|
||||
'port' => 'required|numeric|between:1,65535',
|
||||
'username' => 'required|string|max:32',
|
||||
'password' => 'nullable|string',
|
||||
'node_id' => 'sometimes|nullable|integer|exists:nodes,id',
|
||||
'node_ids' => 'nullable|array',
|
||||
'node_ids.*' => 'required|integer,exists:nodes,id',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
@ -59,7 +60,6 @@ class DatabaseHost extends Model
|
||||
return [
|
||||
'id' => 'integer',
|
||||
'max_databases' => 'integer',
|
||||
'node_id' => 'integer',
|
||||
'password' => 'encrypted',
|
||||
'created_at' => 'immutable_datetime',
|
||||
'updated_at' => 'immutable_datetime',
|
||||
@ -71,12 +71,9 @@ class DatabaseHost extends Model
|
||||
return 'id';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the node associated with a database host.
|
||||
*/
|
||||
public function node(): BelongsTo
|
||||
public function nodes(): BelongsToMany
|
||||
{
|
||||
return $this->belongsTo(Node::class);
|
||||
return $this->belongsToMany(Node::class);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -5,6 +5,7 @@ namespace App\Models;
|
||||
use App\Exceptions\Service\HasActiveServersException;
|
||||
use App\Repositories\Daemon\DaemonConfigurationRepository;
|
||||
use Exception;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
@ -243,9 +244,9 @@ class Node extends Model
|
||||
return $this->hasMany(Allocation::class);
|
||||
}
|
||||
|
||||
public function databaseHosts(): HasMany
|
||||
public function databaseHosts(): BelongsToMany
|
||||
{
|
||||
return $this->hasMany(DatabaseHost::class);
|
||||
return $this->belongsToMany(DatabaseHost::class);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -25,15 +25,15 @@ class DeployServerDatabaseService
|
||||
Assert::notEmpty($data['database'] ?? null);
|
||||
Assert::notEmpty($data['remote'] ?? null);
|
||||
|
||||
$hosts = DatabaseHost::query()->get()->toBase();
|
||||
$hosts = DatabaseHost::query()->get();
|
||||
if ($hosts->isEmpty()) {
|
||||
throw new NoSuitableDatabaseHostException();
|
||||
} else {
|
||||
$nodeHosts = $hosts->where('node_id', $server->node_id)->toBase();
|
||||
}
|
||||
|
||||
if ($nodeHosts->isEmpty() && !config('panel.client_features.databases.allow_random')) {
|
||||
throw new NoSuitableDatabaseHostException();
|
||||
}
|
||||
$nodeHosts = $server->node->databaseHosts()->get();
|
||||
// TODO: @areyouscared remove allow random feature for database hosts
|
||||
if ($nodeHosts->isEmpty() && !config('panel.client_features.databases.allow_random')) {
|
||||
throw new NoSuitableDatabaseHostException();
|
||||
}
|
||||
|
||||
return $this->managementService->create($server, [
|
||||
|
@ -33,9 +33,10 @@ class HostCreationService
|
||||
'port' => array_get($data, 'port'),
|
||||
'username' => array_get($data, 'username'),
|
||||
'max_databases' => array_get($data, 'max_databases'),
|
||||
'node_id' => array_get($data, 'node_id'),
|
||||
]);
|
||||
|
||||
$host->nodes()->sync(array_get($data, 'node_ids', []));
|
||||
|
||||
// Confirm access using the provided credentials before saving data.
|
||||
$this->dynamic->set('dynamic', $host);
|
||||
$this->databaseManager->connection('dynamic')->getPdo();
|
||||
|
@ -5,7 +5,6 @@ namespace App\Transformers\Api\Application;
|
||||
use App\Models\Node;
|
||||
use App\Models\Database;
|
||||
use App\Models\DatabaseHost;
|
||||
use League\Fractal\Resource\Item;
|
||||
use League\Fractal\Resource\Collection;
|
||||
use League\Fractal\Resource\NullResource;
|
||||
|
||||
@ -13,7 +12,7 @@ class DatabaseHostTransformer extends BaseTransformer
|
||||
{
|
||||
protected array $availableIncludes = [
|
||||
'databases',
|
||||
'node',
|
||||
'nodes',
|
||||
];
|
||||
|
||||
/**
|
||||
@ -35,7 +34,6 @@ class DatabaseHostTransformer extends BaseTransformer
|
||||
'host' => $model->host,
|
||||
'port' => $model->port,
|
||||
'username' => $model->username,
|
||||
'node' => $model->node_id,
|
||||
'created_at' => $model->created_at->toAtomString(),
|
||||
'updated_at' => $model->updated_at->toAtomString(),
|
||||
];
|
||||
@ -56,16 +54,16 @@ class DatabaseHostTransformer extends BaseTransformer
|
||||
}
|
||||
|
||||
/**
|
||||
* Include the node associated with this host.
|
||||
* Include the nodes associated with this host.
|
||||
*/
|
||||
public function includeNode(DatabaseHost $model): Item|NullResource
|
||||
public function includeNodes(DatabaseHost $model): Collection|NullResource
|
||||
{
|
||||
if (!$this->authorize(Node::RESOURCE_NAME)) {
|
||||
return $this->null();
|
||||
}
|
||||
|
||||
$model->loadMissing('node');
|
||||
$model->loadMissing('nodes');
|
||||
|
||||
return $this->item($model->getRelation('node'), $this->makeTransformer(NodeTransformer::class), Node::RESOURCE_NAME);
|
||||
return $this->collection($model->getRelation('nodes'), $this->makeTransformer(NodeTransformer::class), Node::RESOURCE_NAME);
|
||||
}
|
||||
}
|
||||
|
@ -34,6 +34,9 @@ return new class extends Migration
|
||||
if (Schema::getConnection()->getDriverName() !== 'sqlite') {
|
||||
$table->dropIndex('permissions_server_id_foreign');
|
||||
$table->dropIndex('permissions_user_id_foreign');
|
||||
} else {
|
||||
$table->dropForeign(['server_id']);
|
||||
$table->dropForeign(['user_id']);
|
||||
}
|
||||
|
||||
$table->dropColumn('server_id');
|
||||
|
@ -13,7 +13,6 @@ return new class extends Migration
|
||||
{
|
||||
Schema::table('servers', function (Blueprint $table) {
|
||||
$table->dropForeign(['pack_id']);
|
||||
|
||||
$table->dropColumn('pack_id');
|
||||
});
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ return new class extends Migration
|
||||
} else {
|
||||
$table->dropForeign(['nest_id']);
|
||||
}
|
||||
|
||||
$table->dropColumn('nest_id');
|
||||
});
|
||||
|
||||
|
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('database_host_node', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedInteger('node_id');
|
||||
$table->foreign('node_id')->references('id')->on('nodes');
|
||||
$table->unsignedInteger('database_host_id');
|
||||
$table->foreign('database_host_id')->references('id')->on('database_hosts');
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
$databaseNodes = DB::table('database_hosts')->whereNotNull('node_id')->get();
|
||||
$newJoinEntries = $databaseNodes->map(fn ($record) => [
|
||||
'node_id' => $record->node_id,
|
||||
'database_host_id' => $record->id,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
DB::table('database_host_node')->insert($newJoinEntries->toArray());
|
||||
|
||||
Schema::table('database_hosts', function (Blueprint $table) {
|
||||
$table->dropForeign(['node_id']);
|
||||
$table->dropColumn('node_id');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('database_hosts', function (Blueprint $table) {
|
||||
$table->unsignedInteger('node_id')->nullable();
|
||||
$table->foreign('node_id')->references('id')->on('nodes');
|
||||
});
|
||||
|
||||
foreach (DB::table('database_host_node')->get() as $record) {
|
||||
DB::table('database_hosts')
|
||||
->where('id', $record->database_host_id)
|
||||
->update(['node_id' => $record->node_id]);
|
||||
}
|
||||
|
||||
Schema::drop('database_host_node');
|
||||
}
|
||||
};
|
@ -52,7 +52,7 @@ class DatabaseManagementServiceTest extends IntegrationTestCase
|
||||
public function testDatabaseCannotBeCreatedIfServerHasReachedLimit(): void
|
||||
{
|
||||
$server = $this->createServerModel(['database_limit' => 2]);
|
||||
$host = DatabaseHost::factory()->create(['node_id' => $server->node_id]);
|
||||
$host = DatabaseHost::factory()->recycle($server->node)->create();
|
||||
|
||||
Database::factory()->times(2)->create(['server_id' => $server->id, 'database_host_id' => $host->id]);
|
||||
|
||||
@ -84,8 +84,8 @@ class DatabaseManagementServiceTest extends IntegrationTestCase
|
||||
$server = $this->createServerModel();
|
||||
$name = DatabaseManagementService::generateUniqueDatabaseName('something', $server->id);
|
||||
|
||||
$host = DatabaseHost::factory()->create(['node_id' => $server->node_id]);
|
||||
$host2 = DatabaseHost::factory()->create(['node_id' => $server->node_id]);
|
||||
$host = DatabaseHost::factory()->recycle($server->node)->create();
|
||||
$host2 = DatabaseHost::factory()->recycle($server->node)->create();
|
||||
Database::factory()->create([
|
||||
'database' => $name,
|
||||
'database_host_id' => $host->id,
|
||||
@ -117,7 +117,7 @@ class DatabaseManagementServiceTest extends IntegrationTestCase
|
||||
$server = $this->createServerModel();
|
||||
$name = DatabaseManagementService::generateUniqueDatabaseName('something', $server->id);
|
||||
|
||||
$host = DatabaseHost::factory()->create(['node_id' => $server->node_id]);
|
||||
$host = DatabaseHost::factory()->recycle($server->node)->create();
|
||||
|
||||
$username = null;
|
||||
$secondUsername = null;
|
||||
@ -154,7 +154,7 @@ class DatabaseManagementServiceTest extends IntegrationTestCase
|
||||
$server = $this->createServerModel();
|
||||
$name = DatabaseManagementService::generateUniqueDatabaseName('something', $server->id);
|
||||
|
||||
$host = DatabaseHost::factory()->create(['node_id' => $server->node_id]);
|
||||
$host = DatabaseHost::factory()->recycle($server->node)->create();
|
||||
|
||||
$this->repository->expects('createDatabase')->with($name)->andThrows(new \BadMethodCallException());
|
||||
$this->repository->expects('dropDatabase')->with($name);
|
||||
|
@ -62,7 +62,7 @@ class DeployServerDatabaseServiceTest extends IntegrationTestCase
|
||||
$server = $this->createServerModel();
|
||||
|
||||
$node = Node::factory()->create();
|
||||
DatabaseHost::factory()->create(['node_id' => $node->id]);
|
||||
DatabaseHost::factory()->recycle($node)->create();
|
||||
|
||||
config()->set('panel.client_features.databases.allow_random', false);
|
||||
|
||||
@ -95,10 +95,7 @@ class DeployServerDatabaseServiceTest extends IntegrationTestCase
|
||||
public function testDatabaseHostOnSameNodeIsPreferred(): void
|
||||
{
|
||||
$server = $this->createServerModel();
|
||||
|
||||
$node = Node::factory()->create();
|
||||
DatabaseHost::factory()->create(['node_id' => $node->id]);
|
||||
$host = DatabaseHost::factory()->create(['node_id' => $server->node_id]);
|
||||
$host = DatabaseHost::factory()->recycle($server->node)->create();
|
||||
|
||||
$this->managementService->expects('create')->with($server, [
|
||||
'database_host_id' => $host->id,
|
||||
@ -124,7 +121,7 @@ class DeployServerDatabaseServiceTest extends IntegrationTestCase
|
||||
$server = $this->createServerModel();
|
||||
|
||||
$node = Node::factory()->create();
|
||||
$host = DatabaseHost::factory()->create(['node_id' => $node->id]);
|
||||
$host = DatabaseHost::factory()->recycle($node)->create();
|
||||
|
||||
$this->managementService->expects('create')->with($server, [
|
||||
'database_host_id' => $host->id,
|
||||
|
Loading…
x
Reference in New Issue
Block a user