feat(dns): Implement DNS management functionality

- Added DnsManager and DnsEntry classes to handle DNS entries.
- Introduced new interfaces for DNS entry requests and data structures.
- Updated Cloudly class to include DnsManager instance.
- Enhanced app state to manage DNS entries and actions for creating, updating, and deleting DNS records.
- Created UI components for DNS management, including forms for adding and editing DNS entries.
- Updated overview and services views to reflect DNS entries.
- Added validation and formatting methods for DNS entries.
This commit is contained in:
2025-09-09 15:08:28 +00:00
parent 38e8b4086d
commit 766191899c
19 changed files with 2174 additions and 99 deletions

View File

@@ -0,0 +1,203 @@
import * as plugins from '../plugins.js';
import { DomainManager } from './classes.domainmanager.js';
export class Domain extends plugins.smartdata.SmartDataDbDoc<
Domain,
plugins.servezoneInterfaces.data.IDomain,
DomainManager
> {
// STATIC
public static async getDomainById(domainIdArg: string) {
const domain = await this.getInstance({
id: domainIdArg,
});
return domain;
}
public static async getDomainByName(domainNameArg: string) {
const domain = await this.getInstance({
'data.name': domainNameArg,
});
return domain;
}
public static async getDomains() {
const domains = await this.getInstances({});
return domains;
}
public static async createDomain(domainDataArg: plugins.servezoneInterfaces.data.IDomain['data']) {
const domain = new Domain();
domain.id = await Domain.getNewId();
domain.data = {
...domainDataArg,
status: domainDataArg.status || 'pending',
verificationStatus: domainDataArg.verificationStatus || 'pending',
nameservers: domainDataArg.nameservers || [],
autoRenew: domainDataArg.autoRenew !== false,
createdAt: Date.now(),
updatedAt: Date.now(),
};
await domain.save();
return domain;
}
public static async updateDomain(
domainIdArg: string,
domainDataArg: Partial<plugins.servezoneInterfaces.data.IDomain['data']>
) {
const domain = await this.getInstance({
id: domainIdArg,
});
if (!domain) {
throw new Error(`Domain with id ${domainIdArg} not found`);
}
Object.assign(domain.data, domainDataArg, {
updatedAt: Date.now(),
});
await domain.save();
return domain;
}
public static async deleteDomain(domainIdArg: string) {
const domain = await this.getInstance({
id: domainIdArg,
});
if (!domain) {
throw new Error(`Domain with id ${domainIdArg} not found`);
}
// Check if there are DNS entries for this domain
const dnsManager = domain.manager.cloudlyRef.dnsManager;
const dnsEntries = await dnsManager.CDnsEntry.getInstances({
'data.zone': domain.data.name,
});
if (dnsEntries.length > 0) {
console.log(`Warning: Deleting domain ${domain.data.name} with ${dnsEntries.length} DNS entries`);
// Optionally delete associated DNS entries
for (const dnsEntry of dnsEntries) {
await dnsEntry.delete();
}
}
await domain.delete();
return true;
}
// INSTANCE
@plugins.smartdata.svDb()
public id: string;
@plugins.smartdata.svDb()
public data: plugins.servezoneInterfaces.data.IDomain['data'];
/**
* Verify domain ownership
*/
public async verifyDomain(methodArg?: 'dns' | 'http' | 'email' | 'manual') {
const method = methodArg || this.data.verificationMethod || 'dns';
// Generate verification token if not exists
if (!this.data.verificationToken) {
this.data.verificationToken = plugins.smartunique.shortId();
await this.save();
}
let verificationResult = {
success: false,
message: '',
details: {} as any,
};
switch (method) {
case 'dns':
// Check for TXT record with verification token
verificationResult = await this.verifyViaDns();
break;
case 'http':
// Check for file at well-known URL
verificationResult = await this.verifyViaHttp();
break;
case 'email':
// Send verification email
verificationResult = await this.verifyViaEmail();
break;
case 'manual':
// Manual verification
verificationResult.success = true;
verificationResult.message = 'Manually verified';
break;
}
// Update verification status
if (verificationResult.success) {
this.data.verificationStatus = 'verified';
this.data.lastVerificationAt = Date.now();
this.data.verificationMethod = method;
} else {
this.data.verificationStatus = 'failed';
this.data.lastVerificationAt = Date.now();
}
await this.save();
return verificationResult;
}
private async verifyViaDns(): Promise<{ success: boolean; message: string; details: any }> {
// TODO: Implement DNS verification
// Look for TXT record _cloudly-verify.{domain} with value {verificationToken}
return {
success: false,
message: 'DNS verification not yet implemented',
details: {
expectedRecord: `_cloudly-verify.${this.data.name}`,
expectedValue: this.data.verificationToken,
},
};
}
private async verifyViaHttp(): Promise<{ success: boolean; message: string; details: any }> {
// TODO: Implement HTTP verification
// Check for file at http://{domain}/.well-known/cloudly-verify.txt
return {
success: false,
message: 'HTTP verification not yet implemented',
details: {
expectedUrl: `http://${this.data.name}/.well-known/cloudly-verify.txt`,
expectedContent: this.data.verificationToken,
},
};
}
private async verifyViaEmail(): Promise<{ success: boolean; message: string; details: any }> {
// TODO: Implement email verification
return {
success: false,
message: 'Email verification not yet implemented',
details: {},
};
}
/**
* Check if domain is expiring soon
*/
public isExpiringSoon(daysThreshold: number = 30): boolean {
if (!this.data.expiresAt) {
return false;
}
const daysUntilExpiry = (this.data.expiresAt - Date.now()) / (1000 * 60 * 60 * 24);
return daysUntilExpiry <= daysThreshold;
}
/**
* Get all DNS entries for this domain
*/
public async getDnsEntries() {
const dnsManager = this.manager.cloudlyRef.dnsManager;
const dnsEntries = await dnsManager.CDnsEntry.getInstances({
'data.zone': this.data.name,
});
return dnsEntries;
}
}