Add all files
This commit is contained in:
594
extensions/Servers/Proxmox/Livewire/Subdomain.php
Normal file
594
extensions/Servers/Proxmox/Livewire/Subdomain.php
Normal file
@@ -0,0 +1,594 @@
|
||||
<?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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user