mirror of
https://github.com/freedbygrace/DynamoDNS.git
synced 2026-07-26 11:38:13 +00:00
Enhance DNS record management by adding auto IP detection, notes field, and improved UI.
Replit-Commit-Author: Agent Replit-Commit-Session-Id: 9111ef36-26c8-4085-84ca-a35dc1fec1b5 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/7083d608-d6d3-4a6a-9a27-6286c5109627/e33594f7-ad24-43f5-a4e1-0d9d2f261947.jpg
This commit is contained in:
@@ -28,6 +28,11 @@ export function Sidebar() {
|
||||
href: "/",
|
||||
icon: <LayoutDashboard className="w-5 h-5 mr-3" />,
|
||||
},
|
||||
{
|
||||
title: "Organizations",
|
||||
href: "/organizations",
|
||||
icon: <Users className="w-5 h-5 mr-3" />,
|
||||
},
|
||||
{
|
||||
title: "Domains",
|
||||
href: "/domains",
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ChevronLeft } from "lucide-react";
|
||||
import { useLocation } from "wouter";
|
||||
|
||||
interface BackButtonProps {
|
||||
to?: string;
|
||||
fallbackPath?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function BackButton({ to, fallbackPath = "/", className = "" }: BackButtonProps) {
|
||||
const [_, setLocation] = useLocation();
|
||||
|
||||
const handleBack = () => {
|
||||
if (to) {
|
||||
setLocation(to);
|
||||
} else if (window.history.length > 2) {
|
||||
window.history.back();
|
||||
} else {
|
||||
setLocation(fallbackPath);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={handleBack}
|
||||
className={`flex items-center gap-1 px-2 ${className}`}
|
||||
>
|
||||
<ChevronLeft className="h-4 w-4" />
|
||||
<span>Back</span>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
@@ -54,10 +54,16 @@ import { Input } from "@/components/ui/input";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger
|
||||
} from "@/components/ui/tooltip";
|
||||
import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbSeparator } from "@/components/ui/breadcrumb";
|
||||
import { RecentActivity } from "@/components/activity/recent-activity";
|
||||
import { Pagination } from "@/components/shared/pagination";
|
||||
import { Loader2, Home, Plus, Pencil, Trash2, ArrowLeft } from "lucide-react";
|
||||
import { Loader2, Home, Plus, Pencil, Trash2, ArrowLeft, FileText } from "lucide-react";
|
||||
import { formatDistanceToNow } from "date-fns";
|
||||
import { Link } from "wouter";
|
||||
|
||||
@@ -69,6 +75,8 @@ const dnsRecordSchema = z.object({
|
||||
ttl: z.number().int().min(1).default(3600),
|
||||
proxied: z.boolean().default(false),
|
||||
isActive: z.boolean().default(true),
|
||||
isAutoIP: z.boolean().default(false),
|
||||
notes: z.string().optional(),
|
||||
});
|
||||
|
||||
export default function DnsRecordsPage() {
|
||||
@@ -113,6 +121,8 @@ export default function DnsRecordsPage() {
|
||||
ttl: 3600,
|
||||
proxied: false,
|
||||
isActive: true,
|
||||
isAutoIP: false,
|
||||
notes: "",
|
||||
},
|
||||
});
|
||||
|
||||
@@ -123,9 +133,11 @@ export default function DnsRecordsPage() {
|
||||
name: selectedRecord.name,
|
||||
type: selectedRecord.type as any,
|
||||
content: selectedRecord.content,
|
||||
ttl: selectedRecord.ttl,
|
||||
proxied: selectedRecord.proxied,
|
||||
ttl: selectedRecord.ttl ?? 3600,
|
||||
proxied: selectedRecord.proxied ?? false,
|
||||
isActive: selectedRecord.isActive,
|
||||
isAutoIP: selectedRecord.isAutoIP ?? false,
|
||||
notes: selectedRecord.notes ?? "",
|
||||
});
|
||||
} else {
|
||||
form.reset({
|
||||
@@ -135,6 +147,8 @@ export default function DnsRecordsPage() {
|
||||
ttl: 3600,
|
||||
proxied: false,
|
||||
isActive: true,
|
||||
isAutoIP: false,
|
||||
notes: "",
|
||||
});
|
||||
}
|
||||
}, [selectedRecord, form]);
|
||||
@@ -289,7 +303,7 @@ export default function DnsRecordsPage() {
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink>{domain?.name || 'Loading...'}</BreadcrumbLink>
|
||||
<BreadcrumbLink href="#">{domain?.name || 'Loading...'}</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
</Breadcrumb>
|
||||
</div>
|
||||
@@ -345,6 +359,11 @@ export default function DnsRecordsPage() {
|
||||
<TableCell className="font-medium">{record.name}</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant="outline">{record.type}</Badge>
|
||||
{record.isAutoIP && (record.type === 'A' || record.type === 'AAAA') && (
|
||||
<Badge variant="outline" className="ml-1 bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300">
|
||||
Auto IP
|
||||
</Badge>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className="font-mono text-sm">
|
||||
{record.content.length > 30
|
||||
@@ -369,21 +388,37 @@ export default function DnsRecordsPage() {
|
||||
: "Never"}
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => handleEditRecord(record)}
|
||||
>
|
||||
<Pencil className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => handleDeleteRecord(record)}
|
||||
className="text-destructive"
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
<div className="flex items-center justify-end">
|
||||
{record.notes && (
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button variant="ghost" size="icon">
|
||||
<FileText className="h-4 w-4 text-muted-foreground" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="left">
|
||||
<p className="font-normal">{record.notes}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
)}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => handleEditRecord(record)}
|
||||
>
|
||||
<Pencil className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => handleDeleteRecord(record)}
|
||||
className="text-destructive"
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
@@ -559,6 +594,46 @@ export default function DnsRecordsPage() {
|
||||
)}
|
||||
/>
|
||||
|
||||
{(form.watch("type") === "A" || form.watch("type") === "AAAA") && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="isAutoIP"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-4">
|
||||
<div className="space-y-0.5">
|
||||
<FormLabel className="text-base">Auto IP Address</FormLabel>
|
||||
<FormDescription>
|
||||
Automatically determine IP address using STUN
|
||||
</FormDescription>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="notes"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Notes</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Optional notes about this record" {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
Add any additional information about this record
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<DialogFooter>
|
||||
<Button
|
||||
type="submit"
|
||||
@@ -724,6 +799,46 @@ export default function DnsRecordsPage() {
|
||||
)}
|
||||
/>
|
||||
|
||||
{(form.watch("type") === "A" || form.watch("type") === "AAAA") && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="isAutoIP"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-4">
|
||||
<div className="space-y-0.5">
|
||||
<FormLabel className="text-base">Auto IP Address</FormLabel>
|
||||
<FormDescription>
|
||||
Automatically determine IP address using STUN
|
||||
</FormDescription>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="notes"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Notes</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Optional notes about this record" {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
Add any additional information about this record
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<DialogFooter>
|
||||
<Button
|
||||
type="submit"
|
||||
|
||||
+36
-6
@@ -271,7 +271,20 @@ export class MemStorage implements IStorage {
|
||||
const id = this.recordIdCounter++;
|
||||
const createdAt = new Date();
|
||||
const lastUpdated = new Date();
|
||||
const newRecord: DnsRecord = { id, ...record, lastUpdated, createdAt };
|
||||
const newRecord: DnsRecord = {
|
||||
id,
|
||||
domainId: record.domainId,
|
||||
name: record.name,
|
||||
type: record.type,
|
||||
content: record.content,
|
||||
ttl: record.ttl ?? 3600,
|
||||
proxied: record.proxied ?? false,
|
||||
isActive: record.isActive ?? true,
|
||||
isAutoIP: record.isAutoIP ?? false,
|
||||
notes: record.notes ?? null,
|
||||
lastUpdated,
|
||||
createdAt
|
||||
};
|
||||
this.recordsMap.set(id, newRecord);
|
||||
return newRecord;
|
||||
}
|
||||
@@ -305,7 +318,14 @@ export class MemStorage implements IStorage {
|
||||
async createProvider(provider: InsertProvider): Promise<Provider> {
|
||||
const id = this.providerIdCounter++;
|
||||
const createdAt = new Date();
|
||||
const newProvider: Provider = { id, ...provider, createdAt };
|
||||
const newProvider: Provider = {
|
||||
id,
|
||||
name: provider.name,
|
||||
type: provider.type,
|
||||
credentials: provider.credentials ?? null,
|
||||
isActive: provider.isActive ?? true,
|
||||
createdAt
|
||||
};
|
||||
this.providersMap.set(id, newProvider);
|
||||
return newProvider;
|
||||
}
|
||||
@@ -341,7 +361,17 @@ export class MemStorage implements IStorage {
|
||||
async createApiToken(token: InsertApiToken): Promise<ApiToken> {
|
||||
const id = this.apiTokenIdCounter++;
|
||||
const createdAt = new Date();
|
||||
const newToken: ApiToken = { id, ...token, createdAt };
|
||||
const newToken: ApiToken = {
|
||||
id,
|
||||
name: token.name,
|
||||
token: token.token,
|
||||
organizationId: token.organizationId,
|
||||
permissions: token.permissions ?? null,
|
||||
createdBy: token.createdBy,
|
||||
isActive: token.isActive ?? true,
|
||||
expiresAt: token.expiresAt ?? null,
|
||||
createdAt
|
||||
};
|
||||
this.apiTokensMap.set(id, newToken);
|
||||
return newToken;
|
||||
}
|
||||
@@ -374,9 +404,9 @@ export class MemStorage implements IStorage {
|
||||
id,
|
||||
recordId,
|
||||
action,
|
||||
previousValue,
|
||||
newValue,
|
||||
userId,
|
||||
previousValue: previousValue ?? null,
|
||||
newValue: newValue ?? null,
|
||||
userId: userId ?? null,
|
||||
timestamp
|
||||
};
|
||||
|
||||
|
||||
@@ -60,6 +60,8 @@ export const dnsRecords = pgTable("dns_records", {
|
||||
ttl: integer("ttl").default(3600),
|
||||
proxied: boolean("proxied").default(false),
|
||||
isActive: boolean("is_active").default(true).notNull(),
|
||||
isAutoIP: boolean("is_auto_ip").default(false).notNull(),
|
||||
notes: text("notes"),
|
||||
lastUpdated: timestamp("last_updated"),
|
||||
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||
});
|
||||
@@ -117,6 +119,8 @@ export const insertDnsRecordSchema = createInsertSchema(dnsRecords).pick({
|
||||
ttl: true,
|
||||
proxied: true,
|
||||
isActive: true,
|
||||
isAutoIP: true,
|
||||
notes: true,
|
||||
});
|
||||
|
||||
export const insertProviderSchema = createInsertSchema(providers).pick({
|
||||
|
||||
Reference in New Issue
Block a user