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,148 @@
import * as plugins from '../plugins.js';
import { DnsManager } from './classes.dnsmanager.js';
export class DnsEntry extends plugins.smartdata.SmartDataDbDoc<
DnsEntry,
plugins.servezoneInterfaces.data.IDnsEntry,
DnsManager
> {
// STATIC
public static async getDnsEntryById(dnsEntryIdArg: string) {
const dnsEntry = await this.getInstance({
id: dnsEntryIdArg,
});
return dnsEntry;
}
public static async getDnsEntries(filterArg?: { zone?: string }) {
const filter: any = {};
if (filterArg?.zone) {
filter['data.zone'] = filterArg.zone;
}
const dnsEntries = await this.getInstances(filter);
return dnsEntries;
}
public static async getDnsZones() {
const dnsEntries = await this.getInstances({});
const zones = new Set<string>();
for (const entry of dnsEntries) {
if (entry.data.zone) {
zones.add(entry.data.zone);
}
}
return Array.from(zones).sort();
}
public static async createDnsEntry(dnsEntryDataArg: plugins.servezoneInterfaces.data.IDnsEntry['data']) {
const dnsEntry = new DnsEntry();
dnsEntry.id = await DnsEntry.getNewId();
dnsEntry.data = {
...dnsEntryDataArg,
ttl: dnsEntryDataArg.ttl || 3600, // Default TTL: 1 hour
active: dnsEntryDataArg.active !== false, // Default to active
createdAt: Date.now(),
updatedAt: Date.now(),
};
await dnsEntry.save();
return dnsEntry;
}
public static async updateDnsEntry(
dnsEntryIdArg: string,
dnsEntryDataArg: Partial<plugins.servezoneInterfaces.data.IDnsEntry['data']>
) {
const dnsEntry = await this.getInstance({
id: dnsEntryIdArg,
});
if (!dnsEntry) {
throw new Error(`DNS entry with id ${dnsEntryIdArg} not found`);
}
Object.assign(dnsEntry.data, dnsEntryDataArg, {
updatedAt: Date.now(),
});
await dnsEntry.save();
return dnsEntry;
}
public static async deleteDnsEntry(dnsEntryIdArg: string) {
const dnsEntry = await this.getInstance({
id: dnsEntryIdArg,
});
if (!dnsEntry) {
throw new Error(`DNS entry with id ${dnsEntryIdArg} not found`);
}
await dnsEntry.delete();
return true;
}
// INSTANCE
@plugins.smartdata.svDb()
public id: string;
@plugins.smartdata.svDb()
public data: plugins.servezoneInterfaces.data.IDnsEntry['data'];
/**
* Validates the DNS entry data
*/
public validateData(): boolean {
const { type, name, value, zone } = this.data;
// Basic validation
if (!type || !name || !value || !zone) {
return false;
}
// Type-specific validation
switch (type) {
case 'A':
// Validate IPv4 address
return /^(\d{1,3}\.){3}\d{1,3}$/.test(value);
case 'AAAA':
// Validate IPv6 address (simplified)
return /^([0-9a-fA-F]{0,4}:){7}[0-9a-fA-F]{0,4}$/.test(value);
case 'MX':
// MX records must have priority
return this.data.priority !== undefined && this.data.priority >= 0;
case 'SRV':
// SRV records must have priority, weight, and port
return (
this.data.priority !== undefined &&
this.data.weight !== undefined &&
this.data.port !== undefined
);
case 'CNAME':
case 'NS':
case 'PTR':
// These should point to valid domain names
return /^[a-zA-Z0-9.-]+$/.test(value);
case 'TXT':
case 'CAA':
case 'SOA':
// These can contain any text
return true;
default:
return false;
}
}
/**
* Get a formatted string representation of the DNS entry
*/
public toFormattedString(): string {
const { type, name, value, ttl, priority } = this.data;
let result = `${name} ${ttl} IN ${type}`;
if (priority !== undefined) {
result += ` ${priority}`;
}
if (type === 'SRV' && this.data.weight !== undefined && this.data.port !== undefined) {
result += ` ${this.data.weight} ${this.data.port}`;
}
result += ` ${value}`;
return result;
}
}

View File

@@ -0,0 +1,152 @@
import type { Cloudly } from '../classes.cloudly.js';
import * as plugins from '../plugins.js';
import { DnsEntry } from './classes.dnsentry.js';
export class DnsManager {
public typedrouter = new plugins.typedrequest.TypedRouter();
public cloudlyRef: Cloudly;
get db() {
return this.cloudlyRef.mongodbConnector.smartdataDb;
}
public CDnsEntry = plugins.smartdata.setDefaultManagerForDoc(this, DnsEntry);
constructor(cloudlyRef: Cloudly) {
this.cloudlyRef = cloudlyRef;
this.cloudlyRef.typedrouter.addTypedRouter(this.typedrouter);
// Get all DNS entries
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.dns.IRequest_Any_Cloudly_GetDnsEntries>(
'getDnsEntries',
async (reqArg) => {
await plugins.smartguard.passGuardsOrReject(reqArg, [
this.cloudlyRef.authManager.validIdentityGuard,
]);
const dnsEntries = await this.CDnsEntry.getDnsEntries(
reqArg.zone ? { zone: reqArg.zone } : undefined
);
return {
dnsEntries: await Promise.all(
dnsEntries.map((entry) => entry.createSavableObject())
),
};
}
)
);
// Get DNS entry by ID
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.dns.IRequest_Any_Cloudly_GetDnsEntryById>(
'getDnsEntryById',
async (reqArg) => {
await plugins.smartguard.passGuardsOrReject(reqArg, [
this.cloudlyRef.authManager.validIdentityGuard,
]);
const dnsEntry = await this.CDnsEntry.getDnsEntryById(reqArg.dnsEntryId);
if (!dnsEntry) {
throw new Error(`DNS entry with id ${reqArg.dnsEntryId} not found`);
}
return {
dnsEntry: await dnsEntry.createSavableObject(),
};
}
)
);
// Create DNS entry
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.dns.IRequest_Any_Cloudly_CreateDnsEntry>(
'createDnsEntry',
async (reqArg) => {
await plugins.smartguard.passGuardsOrReject(reqArg, [
this.cloudlyRef.authManager.validIdentityGuard,
]);
const dnsEntry = await this.CDnsEntry.createDnsEntry(reqArg.dnsEntryData);
return {
dnsEntry: await dnsEntry.createSavableObject(),
};
}
)
);
// Update DNS entry
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.dns.IRequest_Any_Cloudly_UpdateDnsEntry>(
'updateDnsEntry',
async (reqArg) => {
await plugins.smartguard.passGuardsOrReject(reqArg, [
this.cloudlyRef.authManager.validIdentityGuard,
]);
const dnsEntry = await this.CDnsEntry.updateDnsEntry(
reqArg.dnsEntryId,
reqArg.dnsEntryData
);
return {
dnsEntry: await dnsEntry.createSavableObject(),
};
}
)
);
// Delete DNS entry
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.dns.IRequest_Any_Cloudly_DeleteDnsEntry>(
'deleteDnsEntry',
async (reqArg) => {
await plugins.smartguard.passGuardsOrReject(reqArg, [
this.cloudlyRef.authManager.validIdentityGuard,
]);
const success = await this.CDnsEntry.deleteDnsEntry(reqArg.dnsEntryId);
return {
success,
};
}
)
);
// Get DNS zones
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.dns.IRequest_Any_Cloudly_GetDnsZones>(
'getDnsZones',
async (reqArg) => {
await plugins.smartguard.passGuardsOrReject(reqArg, [
this.cloudlyRef.authManager.validIdentityGuard,
]);
const zones = await this.CDnsEntry.getDnsZones();
return {
zones,
};
}
)
);
}
/**
* Initialize the DNS manager
*/
public async init() {
console.log('DNS Manager initialized');
}
/**
* Stop the DNS manager
*/
public async stop() {
console.log('DNS Manager stopped');
}
}