add unfinished backend for ip pool support

This commit is contained in:
Eric Wang
2023-06-13 04:13:52 +00:00
parent c8868a854e
commit 2d5d9becaa
21 changed files with 531 additions and 493 deletions

View File

@@ -0,0 +1,10 @@
<?php
namespace Convoy\Http\Controllers\Admin;
use Convoy\Http\Controllers\ApplicationApiController;
class AddressController extends ApplicationApiController
{
//
}

View File

@@ -0,0 +1,75 @@
<?php
namespace Convoy\Http\Controllers\Admin;
use Convoy\Http\Controllers\ApplicationApiController;
use Convoy\Http\Requests\Admin\AddressPools\StoreAddressPoolRequest;
use Convoy\Http\Requests\Admin\AddressPools\UpdateAddressPoolRequest;
use Convoy\Models\AddressPool;
use Convoy\Models\Filters\FiltersAddressPool;
use Convoy\Models\Filters\FiltersNode;
use Convoy\Transformers\Admin\AddressPoolTransformer;
use Convoy\Transformers\Admin\NodeTransformer;
use Illuminate\Http\Request;
use Spatie\QueryBuilder\AllowedFilter;
use Spatie\QueryBuilder\QueryBuilder;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
class AddressPoolController extends ApplicationApiController
{
public function index(Request $request)
{
$addressPools = QueryBuilder::for(AddressPool::query())
->withCount(['addresses', 'nodes'])
->allowedFilters(['name', AllowedFilter::custom('*', new FiltersAddressPool)])
->paginate(min($request->query('per_page', 50), 100))->appends($request->query());
return fractal($addressPools, new AddressPoolTransformer)->respond();
}
public function show(AddressPool $addressPool)
{
$addressPool->loadCount(['addresses', 'nodes']);
return fractal($addressPool, new AddressPoolTransformer)->respond();
}
public function getNodesAllocatedTo(Request $request, AddressPool $addressPool)
{
$nodes = QueryBuilder::for($addressPool->nodes())
->withCount('servers')
->allowedFilters(['name', 'fqdn', AllowedFilter::exact('location_id'), AllowedFilter::custom('*', new FiltersNode)])
->paginate(min($request->query('per_page', 50), 100))->appends($request->query());
return fractal($nodes, new NodeTransformer())->respond();
}
public function store(StoreAddressPoolRequest $request)
{
$pool = AddressPool::create($request->validated());
$pool->loadCount(['addresses', 'nodes']);
return fractal($pool, new AddressPoolTransformer)->respond();
}
public function update(UpdateAddressPoolRequest $request, AddressPool $addressPool)
{
$addressPool->update($request->validated());
$addressPool->loadCount(['addresses', 'nodes']);
return fractal($addressPool, new AddressPoolTransformer)->respond();
}
public function destroy(AddressPool $addressPool)
{
$addressPool->loadCount('nodes');
if ($addressPool->nodes_count > 0) {
throw new AccessDeniedHttpException('This address pool cannot be deleted while still allocated to nodes.');
}
$addressPool->delete();
return $this->returnNoContent();
}
}

View File

@@ -26,7 +26,6 @@ class NodeController extends ApplicationApiController
public function index(Request $request)
{
$nodes = QueryBuilder::for(Node::query())
->with('servers')
->withCount(['servers'])
->allowedFilters(['name', 'fqdn', AllowedFilter::exact('location_id'), AllowedFilter::custom('*', new FiltersNode)])
->paginate(min($request->query('per_page', 50), 100))->appends($request->query());

View File

@@ -0,0 +1,14 @@
<?php
namespace Convoy\Http\Requests\Admin\AddressPools;
use Convoy\Models\AddressPool;
use Illuminate\Foundation\Http\FormRequest;
class StoreAddressPoolRequest extends FormRequest
{
public function rules(): array
{
return AddressPool::getRules();
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace Convoy\Http\Requests\Admin\AddressPools;
use Convoy\Http\Requests\FormRequest;
use Convoy\Models\AddressPool;
class UpdateAddressPoolRequest extends FormRequest
{
public function rules(): array
{
$addressPool = $this->parameter('address_pool', AddressPool::class);
return AddressPool::getRulesForUpdate($addressPool);
}
}

View File

@@ -7,11 +7,6 @@ use Illuminate\Validation\Validator;
class UpdateGroupOrderRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [

View File

@@ -3,6 +3,7 @@
namespace Convoy\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Address extends Model
{
@@ -22,7 +23,7 @@ class Address extends Model
'mac_address' => ['mac_address', 'nullable'],
];
public function server()
public function server(): BelongsTo
{
return $this->belongsTo(Server::class);
}

View File

@@ -2,10 +2,41 @@
namespace Convoy\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
class AddressPool extends Model
{
use HasFactory;
/**
* Fields that aren't mass assignable
*/
protected $guarded = ['id', 'updated_at', 'created_at'];
public static $validationRules = [
'name' => 'required|string|max:191',
];
/**
* Gets the nodes that an address pool is allocated to.
*/
public function nodes(): BelongsToMany
{
return $this->belongsToMany(Node::class, 'address_pool_to_node', 'address_pool_id', 'node_id');
}
/**
* Gets the addresses that are associated with an address pool.
*/
public function addresses(): HasMany
{
return $this->hasMany(Address::class);
}
/**
* The column Laravel should look at for route model binding.
*/
public function getRouteKeyName(): string
{
return 'id';
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace Convoy\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
/**
* This is a pivot table for linking address pools to nodes in a many-to-many relation.
* An address pool can be allocated to multiple nodes. Similarly, multiple nodes can
* be allocated to a single address pool.
*/
class AddressPoolToNode extends Model
{
/**
* The actual name of the table Laravel ORM should query.
*/
protected $table = 'address_pool_to_node';
public function addressPool(): BelongsTo
{
return $this->belongsTo(AddressPool::class);
}
public function node(): BelongsTo
{
return $this->belongsTo(Node::class);
}
public function addresses(): HasManyThrough
{
return $this->hasManyThrough(Address::class, AddressPool::class);
}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace Convoy\Models\Filters;
use Illuminate\Database\Eloquent\Builder;
use Spatie\QueryBuilder\Filters\Filter;
class FiltersAddressPool implements Filter
{
public function __invoke(Builder $query, $value, string $property)
{
$query->where('id', $value)
->orWhereRaw('LOWER(name) LIKE ?', ["%$value%"]);
}
}

View File

@@ -6,14 +6,30 @@ use Convoy\Casts\MebibytesToAndFromBytes;
use Convoy\Casts\NullableEncrypter;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
class Node extends Model
{
use HasFactory;
/**
* The constants for generating temporary Coterm (a noVNC reverse proxy) session tokens
*/
public const COTERM_TOKEN_ID_LENGTH = 16;
public const COTERM_TOKEN_LENGTH = 64;
/**
* The attributes excluded from the model's JSON form.
*/
protected $hidden = [
'token_id', 'secret', 'coterm_token_id', 'coterm_token',
];
/**
* Cast values to correct type.
*/
protected $casts = [
'memory' => MebibytesToAndFromBytes::class,
'disk' => MebibytesToAndFromBytes::class,
@@ -23,6 +39,9 @@ class Node extends Model
'coterm_token' => NullableEncrypter::class,
];
/**
* Fields that aren't mass assignable
*/
protected $guarded = ['id', 'created_at', 'updated_at'];
public static $validationRules = [
@@ -49,34 +68,62 @@ class Node extends Model
'coterm_token' => 'required_if:coterm_enabled,1',
];
protected $hidden = [
'token_id', 'secret', 'coterm_token_id', 'coterm_token',
];
/**
* Get the connection address to use when making calls to this node.
* Get the connection address to use when making calls to this node's assigned Coterm endpoint.
*/
public function getCotermConnectionAddress(): string
{
return sprintf('%s://%s:%s', $this->coterm_tls_enabled ? 'https' : 'http', $this->coterm_fqdn, $this->coterm_port);
}
public function servers()
/**
* Gets the servers associated with a node.
*/
public function servers(): HasMany
{
return $this->hasMany(Server::class);
}
public function addresses()
/**
* Gets the address pools allocated to a node.
*/
public function addressPools(): BelongsToMany
{
return $this->hasMany(Address::class);
return $this->belongsToMany(
AddressPool::class,
'address_pool_to_node',
'node_id',
'address_pool_id'
);
}
public function templateGroups()
/**
* Gets all the addresses associated with a node from the address pool(s) allocated to a node.
*/
public function addresses(): HasManyThrough
{
return $this->hasManyThrough(
Address::class,
AddressPoolToNode::class,
'node_id',
'address_pool_id',
'id',
'address_pool_id'
);
}
/**
* Gets the template groups associated with a node. This is not the same as TEMPLATES.
*/
public function templateGroups(): HasMany
{
return $this->hasMany(TemplateGroup::class);
}
public function isos()
/**
* Gets the ISOs downloaded on a node.
*/
public function isos(): HasMany
{
return $this->hasMany(ISO::class);
}
@@ -89,18 +136,27 @@ class Node extends Model
return $this->belongsTo(Location::class);
}
public function getRouteKeyName(): string
{
return 'id';
}
public function getDiskAllocatedAttribute()
/**
* Gets the total disk used from adding up all the associated servers' disk sizes.
*/
public function getDiskAllocatedAttribute(): int
{
return $this->servers->sum('disk');
}
public function getMemoryAllocatedAttribute()
/**
* Gets the total memory used from adding up all the associated servers' allocated memory.
*/
public function getMemoryAllocatedAttribute(): int
{
return $this->servers->sum('memory');
}
/**
* The column Laravel should look at for route model binding.
*/
public function getRouteKeyName(): string
{
return 'id';
}
}

View File

@@ -9,8 +9,6 @@ class PersonalAccessToken extends SanctumPersonalAccessToken
{
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'type',

View File

@@ -5,13 +5,12 @@ namespace Convoy\Services\Servers;
use Convoy\Data\Server\Eloquent\ServerEloquentData;
use Convoy\Data\Server\Proxmox\ServerProxmoxData;
use Convoy\Models\Server;
use Convoy\Repositories\Proxmox\Server\ProxmoxCloudinitRepository;
use Convoy\Repositories\Proxmox\Server\ProxmoxConfigRepository;
use Illuminate\Support\Arr;
class ServerDetailService
{
public function __construct(private NetworkService $networkService, private ProxmoxConfigRepository $allocationRepository, private ProxmoxCloudinitRepository $cloudinitRepository, private AllocationService $allocationService, private CloudinitService $cloudinitService)
public function __construct(private NetworkService $networkService, private ProxmoxConfigRepository $allocationRepository, private AllocationService $allocationService,)
{
}

View File

@@ -0,0 +1,19 @@
<?php
namespace Convoy\Transformers\Admin;
use Convoy\Models\AddressPool;
use League\Fractal\TransformerAbstract;
class AddressPoolTransformer extends TransformerAbstract
{
public function transform(AddressPool $pool): array
{
return [
'id' => $pool->id,
'name' => $pool->name,
'nodes_count' => (int) $pool->nodes_count,
'addresses_count' => (int) $pool->addresses_count,
];
}
}

View File

@@ -95,4 +95,5 @@ return [
'iso' => 'ISO',
'iso_one' => 'ISO',
'iso_other' => 'ISOs',
'ipam' => 'IPAM',
];

683
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -19,9 +19,7 @@
"@mantine/notifications": "^5.9.4",
"@mantine/nprogress": "^5.9.4",
"@tailwindcss/forms": "^0.5.2",
"@tanstack/react-query": "^4.0.5",
"@tanstack/react-table": "^8.7.0",
"@vitejs/plugin-react": "^1.3.2",
"autoprefixer": "^10.4.2",
"axios": "^0.25.0",
"chart.js": "^3.8.0",

View File

@@ -0,0 +1,3 @@
const IpamContainer = () => {}
export default IpamContainer

View File

@@ -7,7 +7,7 @@ interface Props {
children: ReactNode
}
const Drawer = forwardRef<HTMLDivElement, Props>(({ open, onClose, children }: Props, ref) => {
const Drawer = forwardRef<HTMLDivElement, Props>(({ open, onClose, children }, ref) => {
const focusTrapRef = useRef(null)
return (

View File

@@ -71,6 +71,10 @@ const AdminDashboardRouter = () => {
name: tStrings('server_other'),
path: '/admin/servers',
},
{
name: tStrings('ipam'),
path: '/admin/ipam',
},
{
name: tStrings('user_other'),
path: '/admin/users',

View File

@@ -48,6 +48,10 @@ Route::prefix('/servers/{server}')->middleware(ValidateServerStatusMiddleware::c
});
});
Route::resource('address-pools', Admin\AddressPoolController::class)
->only(['index', 'show', 'store', 'update', 'destroy']);
Route::get('/address-pools/{address_pool}/nodes', [Admin\AddressPoolController::class, 'getNodesAllocatedTo']);
Route::resource('users', Admin\UserController::class)
->only(['index', 'show', 'store', 'update', 'destroy']);