595 lines
24 KiB
PHP
595 lines
24 KiB
PHP
<?php
|
|
|
|
namespace Paymenter\Extensions\Servers\Proxmox\Livewire;
|
|
|
|
use Illuminate\Support\Arr;
|
|
use Illuminate\Support\Str;
|
|
use Paymenter\Extensions\Servers\Proxmox\Models\Server;
|
|
use Paymenter\Extensions\Servers\Proxmox\Models\IPAddress;
|
|
use Paymenter\Extensions\Servers\Proxmox\TechnitiumDns;
|
|
|
|
class Subdomain extends Component
|
|
{
|
|
public string $newSubdomain = '';
|
|
public ?int $selectedIpId = null;
|
|
public array $serverZones = [];
|
|
public bool $showCreateModal = false;
|
|
public bool $showRecordModal = false;
|
|
public bool $showDeleteModal = false;
|
|
public ?string $subdomainToDelete = null;
|
|
public ?int $ipAddressIdToDelete = null;
|
|
public ?string $subdomainToDeleteFull = null;
|
|
public ?string $selectedZone = null;
|
|
public string $recordName = '';
|
|
public string $recordType = 'A';
|
|
public string $recordRdata = '';
|
|
public int $recordTtl = 3600;
|
|
public array $zoneRecords = [];
|
|
protected array $baseAdjectives = [
|
|
'ancient','agile','amazing','amber','ample','arcane','arctic','artful','atomic','attuned',
|
|
'balanced','bare','basic','bashful','beauteous','beloved','better','beyond','big','bitter',
|
|
'blazing','bleak','blind','blissful','blue','bold','brave','breezy','bright','brilliant',
|
|
'brisk','broad','broken','burning','buzzing','calm','capable','careful','casual','celestial',
|
|
'certain','charmed','cheerful','chief','chilly','choice','chosen','circular','classic','clean',
|
|
'clear','clever','close','cloudy','coastal','cold','colossal','cosmic','courageous','crimson',
|
|
'crisp','critical','cunning','curious','daily','daring','dark','deep','defiant','delicate',
|
|
'deserted','detailed','diligent','distant','divine','dormant','double','drifting','driven','dry',
|
|
'eager','early','earnest','earthy','easy','ebony','echoing','ecstatic','electric','elegant',
|
|
'elite','emerald','endless','eternal','even','every','evil','exact','exalted','excited',
|
|
'exotic','fabled','faint','fair','faithful','fancy','far','faster','fat','fearless',
|
|
'feathered','ferocious','fiery','final','fine','firm','first','fitting','flawless','fleeting',
|
|
'flexible','fluffy','flying','focused','fond','forever','forgotten','fortunate','fragile','free',
|
|
'fresh','friendly','frosty','frozen','full','funny','furry','fussy','galactic','gentle',
|
|
'genuine','giant','gilded','giving','glad','gleaming','glimmering','global','glorious','golden',
|
|
'good','grand','grateful','grave','gray','great','green','growing','guarded','gutsy',
|
|
'happy','hardy','harsh','hasty','haunted','hearty','heavenly','heavy','helpful','heroic',
|
|
'hidden','high','hollow','holy','honest','hopeful','horned','hot','huge','humble',
|
|
'icy','ideal','idle','illuminated','imagined','immense','immune','imperial','important','infinite',
|
|
'innocent','inner','inspired','intense','intrepid','iron','ivory','jaded','jazzy','joyful',
|
|
'juicy','jumbo','just','keen','kind','kingly','knowing','kosmic','labeled','lame',
|
|
'lanky','large','lasting','lavish','lawful','lazy','leading','leafy','lean','leaping',
|
|
'legendary','legible','light','lilac','limber','limited','lively','living','lonely','long',
|
|
'loose','loud','loving','lucid','lucky','lunar','lush','mad','magic','magnetic',
|
|
'magnificent','major','mammoth','mellow','merry','mighty','mild','mini','minute','mirrored',
|
|
'misty','modern','modest','molten','moonlit','moral','mossy','muddy','mystic','naive',
|
|
'narrow','nascent','native','natural','nautical','near','neat','nebulous','needed','nervy',
|
|
'new','next','nice','nifty','nimble','noble','noisy','normal','northern','notable',
|
|
'novel','numerous','nutty','oaken','oasis','obedient','obvious','oceanic','odd','official',
|
|
'old','olive','ominous','only','open','optimal','orange','ordinary','organic','origin',
|
|
'ornate','outer','overdue','overt','pacific','pale','parallel','passive','patient','peaceful',
|
|
'peach','pearl','perfect','perilous','petite','phobic','phoenix','pinnacle','pink','pious',
|
|
'pivotal','plain','playful','pleasant','plentiful','pliant','plucky','plump','polar','polished',
|
|
'poor','popular','positive','possible','potent','powerful','precious','pretty','prickly','primal',
|
|
'primed','proper','proud','public','puffy','pulsing','pure','purple','quaint','quick',
|
|
'quiet','radiant','ragged','random','rapid','rare','raspy','raw','ready','real',
|
|
'rebel','reckless','regal','regular','relaxed','reliable','remarkable','remote','resting','revered',
|
|
'rich','rigid','ripe','rising','roaring','robust','rocky','rosy','rough','round',
|
|
'royal','ruddy','rugged','rusted','rustic','sacred','safe','sandy','savage','scarlet',
|
|
'scary','scenic','secret','serene','sharp','shining','shiny','shivering','shocking','short',
|
|
'shy','silent','silver','simple','sincere','single','sizzling','skilled','sleepy','slender',
|
|
'slimy','slow','smart','smoky','smooth','snappy','snarling','sneaky','snug','soaring',
|
|
'solar','solemn','solid','somber','sonic','soothing','soulful','southern','spacious','sparkling',
|
|
'special','speedy','spicy','spiky','spiral','spirited','splendid','spooky','spotless','spry',
|
|
'square','squeaky','stable','starry','steadfast','steady','steel','steep','stellar','sticky',
|
|
'stout','strange','strong','sturdy','subtle','sudden','sugary','sunny','super','sure',
|
|
'surreal','swift','tall','tame','tasty','teal','telling','tense','tender','terrific',
|
|
'thick','thin','third','thorny','thoughtful','thrifty','thunderous','tidy','timber','timid',
|
|
'tiny','tired','titanic','toasty','tough','tranquil','trembling','triangular','true','trusted',
|
|
'twilight','ultimate','ultra','united','unique','untamed','upbeat','upright','upward','urban',
|
|
'usable','useful','usual','utopian','valiant','vast','velvet','verdant','vibrant','vicious',
|
|
'vigilant','vintage','violet','virtuous','vivid','vocal','volatile','wandering','warm','warped',
|
|
'watery','wealthy','weary','weathered','weird','western','whimsical','whispering','white','wicked',
|
|
'wild','willing','windy','winning','winter','wise','witty','wobbly','wooden','woolly',
|
|
'worthy','wounded','young','yummy','zealous','zesty','zigzag','zinc','zippy','zonal',
|
|
];
|
|
|
|
protected array $baseAnimals = [
|
|
'aardvark','albatross','alligator','alpaca','ant','anteater','antelope','armadillo','asp','auk',
|
|
'baboon','badger','bandicoot','barnacle','barracuda','bat','bear','beaver','bee','beetle',
|
|
'bison','boar','bobcat','buffalo','bull','bunny','butterfly','calf','camel','canary',
|
|
'caribou','cat','caterpillar','catfish','cheetah','chicken','chimp','chinchilla','chipmunk','cobra',
|
|
'condor','cougar','cow','coyote','crab','crane','cricket','crow','deer','dingo',
|
|
'dog','dolphin','donkey','dove','dragon','duck','eagle','earthworm','eel','egret',
|
|
'elephant','elk','emu','falcon','ferret','finch','fish','flamingo','fox','frog',
|
|
'gazelle','gecko','gerbil','giraffe','goat','goose','gorilla','gopher','grizzly','grouse',
|
|
'guanaco','gull','hamster','hare','hawk','hedgehog','heron','hippo','horse','hound',
|
|
'hyena','ibis','iguana','impala','jackal','jaguar','jay','jellyfish','kangaroo','kingfisher',
|
|
'koala','koi','kraken','lemming','lemur','leopard','lion','lizard','llama','lobster',
|
|
'lynx','macaw','magpie','mallard','manatee','mandrill','marten','meerkat','mink','mole',
|
|
'monkey','moose','moray','mouse','mule','narwhal','newt','nightjar','ocelot','octopus',
|
|
'opossum','orca','osprey','ostrich','otter','owl','ox','panda','panther','parrot',
|
|
'peacock','pelican','penguin','pheasant','pig','pigeon','piranha','pitbull','platypus','pony',
|
|
'porcupine','possum','puffin','puma','python','quail','quokka','rabbit','raccoon','ram',
|
|
'raven','reindeer','rhino','robin','rooster','salamander','salmon','seal','serpent','shark',
|
|
'sheep','shrew','shrimp','skink','skunk','sloth','snail','snake','sparrow','spider',
|
|
'squid','squirrel','stallion','starling','stingray','stoat','stork','swan','tapir','tiger',
|
|
'toad','toucan','trout','turkey','turtle','urial','viper','vixen','vulture','wallaby',
|
|
'walrus','warthog','weasel','whale','wolf','wolverine','wombat','woodpecker','yak','zebra',
|
|
];
|
|
|
|
public function mount(Server $server): void
|
|
{
|
|
parent::mount($server);
|
|
$this->loadServerZones();
|
|
}
|
|
|
|
/**
|
|
* Get public IP addresses for the server
|
|
*/
|
|
public function getPublicIpsProperty(): array
|
|
{
|
|
$publicIps = [];
|
|
|
|
foreach ($this->server->ipAddresses as $ip) {
|
|
if ($this->isPublicIp($ip->ip, $ip->type)) {
|
|
$publicIps[] = $ip;
|
|
}
|
|
}
|
|
|
|
return $publicIps;
|
|
}
|
|
|
|
/**
|
|
* Check if IP is public
|
|
*/
|
|
private function isPublicIp(string $ip, string $type = 'ipv4'): bool
|
|
{
|
|
if ($type === 'ipv6' || filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
|
|
if ($ip === '::1' || strpos($ip, '::1/') === 0) {
|
|
return false;
|
|
}
|
|
if (preg_match('/^fe[89ab][0-9a-f]:/i', $ip)) {
|
|
return false;
|
|
}
|
|
if (preg_match('/^fc[0-9a-f]{1,2}:/i', $ip) || preg_match('/^fd[0-9a-f]{1,2}:/i', $ip)) {
|
|
return false;
|
|
}
|
|
if (strpos($ip, '::ffff:') === 0 || strpos($ip, '::ffff:') !== false) {
|
|
return false;
|
|
}
|
|
return true;
|
|
} else {
|
|
$parts = explode('.', $ip);
|
|
if (count($parts) !== 4) {
|
|
return false;
|
|
}
|
|
$first = (int)$parts[0];
|
|
$second = (int)$parts[1];
|
|
|
|
if ($first === 10) {
|
|
return false;
|
|
}
|
|
if ($first === 172 && $second >= 16 && $second <= 31) {
|
|
return false;
|
|
}
|
|
if ($first === 192 && $second === 168) {
|
|
return false;
|
|
}
|
|
if ($first === 127) {
|
|
return false;
|
|
}
|
|
if ($first === 169 && $second === 254) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Load zones associated with this server
|
|
*/
|
|
public function loadServerZones(): void
|
|
{
|
|
try {
|
|
$dns = new TechnitiumDns;
|
|
$this->serverZones = $dns->getServerZones($this->server->id);
|
|
} catch (\Exception $e) {
|
|
$this->serverZones = [];
|
|
$this->notify('Failed to load subdomains: ' . $e->getMessage(), 'error');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if subdomain is available
|
|
*/
|
|
public function checkSubdomainAvailability(): void
|
|
{
|
|
if (empty($this->newSubdomain)) {
|
|
return;
|
|
}
|
|
|
|
// Validate subdomain format
|
|
if (!preg_match('/^[a-z0-9]([a-z0-9\-]{0,61}[a-z0-9])?$/i', $this->newSubdomain)) {
|
|
$this->notify('Invalid subdomain format. Use only letters, numbers, and hyphens.', 'error');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$dns = new TechnitiumDns;
|
|
if ($dns->subdomainExists($this->newSubdomain)) {
|
|
$this->notify('This subdomain is already taken.', 'error');
|
|
} else {
|
|
$this->notify('Subdomain is available!', 'success');
|
|
}
|
|
} catch (\Exception $e) {
|
|
$this->notify('Failed to check subdomain availability: ' . $e->getMessage(), 'error');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create a new subdomain
|
|
*/
|
|
public function createSubdomain(): void
|
|
{
|
|
$this->validate([
|
|
'newSubdomain' => 'required|string|regex:/^[a-z0-9]([a-z0-9\-]{0,61}[a-z0-9])?$/i',
|
|
'selectedIpId' => 'required|exists:ext_proxmox_ip_addresses,id',
|
|
], [
|
|
'newSubdomain.regex' => 'Invalid subdomain format. Use only letters, numbers, and hyphens.',
|
|
'selectedIpId.required' => 'Please select an IP address.',
|
|
]);
|
|
|
|
// Check if user already has a subdomain for this IP
|
|
$selectedIp = $this->server->ipAddresses->firstWhere('id', $this->selectedIpId);
|
|
if (!$selectedIp) {
|
|
$this->notify('Selected IP address not found.', 'error');
|
|
return;
|
|
}
|
|
|
|
// Check if subdomain already exists in database
|
|
try {
|
|
// Determine the selected IP's type (IPv4 or IPv6)
|
|
$selectedIpType = filter_var($selectedIp->ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) ? 'ipv6' : 'ipv4';
|
|
$recordType = $selectedIpType === 'ipv6' ? 'AAAA' : 'A';
|
|
|
|
// Check if user already has a subdomain for this specific IP address
|
|
$selectedIpSubdomainData = $this->normalizeSubdomainData($selectedIp->subdomain);
|
|
if ($selectedIpSubdomainData && !empty($selectedIpSubdomainData['subdomain'])) {
|
|
$this->notify('You already have a subdomain for this IP address.', 'error');
|
|
return;
|
|
}
|
|
|
|
// Load every subdomain entry in the database to ensure global uniqueness rules
|
|
$allSubdomainEntries = IPAddress::query()
|
|
->whereNotNull('subdomain')
|
|
->get();
|
|
|
|
$existingIpv4Count = 0;
|
|
$existingIpv6Count = 0;
|
|
|
|
foreach ($allSubdomainEntries as $existingIp) {
|
|
$subdomainData = $this->normalizeSubdomainData($existingIp->subdomain);
|
|
if (!$subdomainData) {
|
|
continue;
|
|
}
|
|
|
|
$existingSubdomainName = $subdomainData['subdomain'] ?? null;
|
|
if ($existingSubdomainName === $this->newSubdomain) {
|
|
$existingIpType = filter_var($existingIp->ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) ? 'ipv6' : 'ipv4';
|
|
|
|
if ($existingIpType === 'ipv4') {
|
|
$existingIpv4Count++;
|
|
} else {
|
|
$existingIpv6Count++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check if we're trying to create a duplicate of the same IP type
|
|
if ($selectedIpType === 'ipv4' && $existingIpv4Count > 0) {
|
|
$this->notify('This subdomain already exists for an IPv4 address on this server. Please choose a different name.', 'error');
|
|
return;
|
|
}
|
|
|
|
if ($selectedIpType === 'ipv6' && $existingIpv6Count > 0) {
|
|
$this->notify('This subdomain already exists for an IPv6 address on this server. Please choose a different name.', 'error');
|
|
return;
|
|
}
|
|
|
|
// Check if we already have both IPv4 and IPv6 for this subdomain
|
|
if ($existingIpv4Count > 0 && $existingIpv6Count > 0) {
|
|
$this->notify('This subdomain already exists for both IPv4 and IPv6 addresses on this server. Please choose a different name.', 'error');
|
|
return;
|
|
}
|
|
|
|
// Create the subdomain via Technitium DNS API
|
|
$dns = new TechnitiumDns;
|
|
$result = $dns->createZone($this->newSubdomain, $selectedIp->ip, $this->server->id, $selectedIp->id);
|
|
|
|
if (($result['success'] ?? false) === true) {
|
|
$recordType = $result['recordType'] ?? ($selectedIpType === 'ipv6' ? 'AAAA' : 'A');
|
|
|
|
$records = [
|
|
['name' => '@', 'type' => $recordType, 'rdata' => $selectedIp->ip, 'ttl' => 3600],
|
|
];
|
|
|
|
if (($result['wildcardCreated'] ?? false) === true) {
|
|
$records[] = ['name' => '*', 'type' => $recordType, 'rdata' => $selectedIp->ip, 'ttl' => 3600];
|
|
}
|
|
|
|
$selectedIp->subdomain = [
|
|
'subdomain' => $this->newSubdomain,
|
|
'fullDomain' => $this->newSubdomain . '.dns.virtnet.host',
|
|
'ipAddress' => $selectedIp->ip,
|
|
'records' => $records,
|
|
];
|
|
$selectedIp->save();
|
|
$selectedIp->refresh();
|
|
// Refresh the server's IP addresses collection
|
|
$this->server->load('ipAddresses');
|
|
|
|
$this->notify('Subdomain created successfully!', 'success');
|
|
$this->newSubdomain = '';
|
|
$this->selectedIpId = null;
|
|
$this->showCreateModal = false;
|
|
$this->loadServerZones();
|
|
} else {
|
|
$errorMessage = $result['message'] ?? 'Failed to create subdomain.';
|
|
$this->notify($errorMessage, 'error');
|
|
}
|
|
} catch (\Exception $e) {
|
|
$this->notify('Failed to create subdomain: ' . $e->getMessage(), 'error');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generate a random subdomain from word pool
|
|
*/
|
|
public function generateRandomSubdomain(): void
|
|
{
|
|
$adjectives = $this->adjectivePool();
|
|
$animals = $this->animalPool();
|
|
|
|
$adjective = Str::slug(Arr::random($adjectives), '');
|
|
$animal = Str::slug(Arr::random($animals), '');
|
|
|
|
$this->newSubdomain = strtolower($adjective . '-' . $animal);
|
|
}
|
|
|
|
protected function adjectivePool(): array
|
|
{
|
|
static $pool = null;
|
|
|
|
if ($pool === null) {
|
|
$pool = $this->expandPool($this->baseAdjectives);
|
|
}
|
|
|
|
return $pool;
|
|
}
|
|
|
|
protected function animalPool(): array
|
|
{
|
|
static $pool = null;
|
|
|
|
if ($pool === null) {
|
|
$pool = $this->expandPool($this->baseAnimals);
|
|
}
|
|
|
|
return $pool;
|
|
}
|
|
|
|
protected function expandPool(array $base): array
|
|
{
|
|
if (count($base) >= 1000) {
|
|
return array_slice($base, 0, 1000);
|
|
}
|
|
|
|
$pool = $base;
|
|
while (count($pool) < 1000) {
|
|
$pool = array_merge($pool, $base);
|
|
}
|
|
|
|
return array_slice($pool, 0, 1000);
|
|
}
|
|
|
|
/**
|
|
* Open delete confirmation modal
|
|
*/
|
|
public function openDeleteModal(string $subdomain, int $ipAddressId): void
|
|
{
|
|
$this->subdomainToDelete = $subdomain;
|
|
$this->ipAddressIdToDelete = $ipAddressId;
|
|
$this->subdomainToDeleteFull = $subdomain . '.dns.virtnet.host';
|
|
$this->showDeleteModal = true;
|
|
}
|
|
|
|
/**
|
|
* Close delete confirmation modal
|
|
*/
|
|
public function closeDeleteModal(): void
|
|
{
|
|
$this->showDeleteModal = false;
|
|
$this->subdomainToDelete = null;
|
|
$this->ipAddressIdToDelete = null;
|
|
$this->subdomainToDeleteFull = null;
|
|
}
|
|
|
|
/**
|
|
* Delete a subdomain
|
|
*/
|
|
public function deleteSubdomain(): void
|
|
{
|
|
if (!$this->subdomainToDelete || !$this->ipAddressIdToDelete) {
|
|
$this->notify('Invalid subdomain selection.', 'error');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$dns = new TechnitiumDns;
|
|
if ($dns->deleteZone($this->subdomainToDelete, $this->ipAddressIdToDelete)) {
|
|
$this->notify('Subdomain deleted successfully!', 'success');
|
|
$this->loadServerZones();
|
|
$this->closeDeleteModal();
|
|
} else {
|
|
$this->notify('Failed to delete subdomain.', 'error');
|
|
}
|
|
} catch (\Exception $e) {
|
|
$this->notify('Failed to delete subdomain: ' . $e->getMessage(), 'error');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Open record creation modal
|
|
*/
|
|
public function openRecordModal(string $subdomain): void
|
|
{
|
|
$this->selectedZone = $subdomain;
|
|
$this->recordName = '';
|
|
$this->recordType = 'A';
|
|
$this->recordRdata = '';
|
|
$this->recordTtl = 3600;
|
|
$this->loadZoneRecords();
|
|
$this->showRecordModal = true;
|
|
}
|
|
|
|
/**
|
|
* Load records for a zone
|
|
*/
|
|
public function loadZoneRecords(): void
|
|
{
|
|
if (!$this->selectedZone) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$dns = new TechnitiumDns;
|
|
$records = $dns->getZoneRecords($this->selectedZone);
|
|
// Ensure we have an array and it's properly formatted
|
|
$this->zoneRecords = is_array($records) ? $records : [];
|
|
} catch (\Exception $e) {
|
|
$this->zoneRecords = [];
|
|
// Don't show error on every load, only log it
|
|
\Log::debug('Failed to load DNS records: ' . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add a DNS record
|
|
*/
|
|
public function addRecord(): void
|
|
{
|
|
$this->validate([
|
|
'recordName' => 'required|string',
|
|
'recordType' => 'required|in:A,AAAA',
|
|
'recordRdata' => 'required|string',
|
|
'recordTtl' => 'required|integer|min:60|max:86400',
|
|
]);
|
|
|
|
if (!$this->selectedZone) {
|
|
$this->notify('No zone selected.', 'error');
|
|
return;
|
|
}
|
|
|
|
// Validate IP address format
|
|
if ($this->recordType === 'A' && !filter_var($this->recordRdata, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
|
|
$this->notify('Invalid IPv4 address.', 'error');
|
|
return;
|
|
}
|
|
if ($this->recordType === 'AAAA' && !filter_var($this->recordRdata, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
|
|
$this->notify('Invalid IPv6 address.', 'error');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$dns = new TechnitiumDns;
|
|
if ($dns->addRecord($this->selectedZone, $this->recordName, $this->recordType, $this->recordRdata, $this->recordTtl)) {
|
|
$this->notify('DNS record added successfully!', 'success');
|
|
$this->recordName = '';
|
|
$this->recordRdata = '';
|
|
|
|
// Small delay to ensure API has processed the record
|
|
usleep(500000); // 0.5 seconds
|
|
|
|
// Reload records from DNS API to get the latest data
|
|
$this->loadZoneRecords();
|
|
} else {
|
|
$this->notify('Failed to add DNS record.', 'error');
|
|
}
|
|
} catch (\Exception $e) {
|
|
$this->notify('Failed to add DNS record: ' . $e->getMessage(), 'error');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Delete a DNS record
|
|
*/
|
|
public function deleteRecord(string $name, string $type): void
|
|
{
|
|
if (!$this->selectedZone) {
|
|
$this->notify('No zone selected.', 'error');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// Find the record to get its IP address
|
|
$ipAddress = null;
|
|
foreach ($this->zoneRecords as $record) {
|
|
if (($record['name'] ?? '') === $name && ($record['type'] ?? '') === $type) {
|
|
$ipAddress = $record['rdata'] ?? $record['ipAddress'] ?? null;
|
|
break;
|
|
}
|
|
}
|
|
|
|
$dns = new TechnitiumDns;
|
|
if ($dns->deleteRecord($this->selectedZone, $name, $type, $ipAddress)) {
|
|
// Update database with new record list
|
|
$this->updateSubdomainRecords();
|
|
|
|
$this->notify('DNS record deleted successfully!', 'success');
|
|
$this->loadZoneRecords();
|
|
} else {
|
|
$this->notify('Failed to delete DNS record.', 'error');
|
|
}
|
|
} catch (\Exception $e) {
|
|
$this->notify('Failed to delete DNS record: ' . $e->getMessage(), 'error');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get available IPs for creating subdomains
|
|
*/
|
|
public function getAvailableIpsForSubdomainProperty(): array
|
|
{
|
|
$publicIps = $this->publicIps;
|
|
|
|
// Filter out IPs that already have subdomains
|
|
return collect($publicIps)->filter(function ($ip) {
|
|
// Check if this IP already has a subdomain assigned
|
|
$subdomainData = $this->normalizeSubdomainData($ip->subdomain);
|
|
if ($subdomainData && !empty($subdomainData['subdomain'])) {
|
|
return false;
|
|
}
|
|
return true;
|
|
})->values()->all();
|
|
}
|
|
|
|
/**
|
|
* Normalize subdomain storage values to an array regardless of DB column type
|
|
*/
|
|
private function normalizeSubdomainData(mixed $value): ?array
|
|
{
|
|
if (!$value) {
|
|
return null;
|
|
}
|
|
|
|
if (is_array($value)) {
|
|
return $value;
|
|
}
|
|
|
|
if (is_string($value)) {
|
|
$decoded = json_decode($value, true);
|
|
return is_array($decoded) ? $decoded : null;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public function render()
|
|
{
|
|
return view('proxmox::subdomain')->layoutData([
|
|
'sidebar' => true,
|
|
]);
|
|
}
|
|
}
|
|
|