diff --git a/app/api/dhcp/leases/route.js b/app/api/dhcp/leases/route.js new file mode 100644 index 0000000..10e45ed --- /dev/null +++ b/app/api/dhcp/leases/route.js @@ -0,0 +1,47 @@ +import { NextResponse } from 'next/server'; +import fs from 'fs'; +import path from 'path'; + +const leasesFilePath = '/var/lib/dhcp/dhcpd.leases'; + +function parseLeases(fileContent) { + const leases = []; + const leaseBlocks = fileContent.split('lease '); + + leaseBlocks.shift(); // Remove the first empty block + + leaseBlocks.forEach((block) => { + const lease = {}; + const lines = block.split('\n'); + lease.ip = lines[0].trim(); + + lines.forEach((line) => { + line = line.trim(); + if (line.startsWith('starts')) { + lease.start = line.split(' ')[2] + ' ' + line.split(' ')[3]; + } else if (line.startsWith('ends')) { + lease.end = line.split(' ')[2] + ' ' + line.split(' ')[3]; + } else if (line.startsWith('binding state')) { + lease.state = line.split(' ')[2]; + } else if (line.startsWith('hardware ethernet')) { + lease.mac = line.split(' ')[2].replace(';', ''); + } else if (line.startsWith('client-hostname')) { + lease.hostname = line.split(' ')[1].replace(';', '').replace(/"/g, ''); + } + }); + + leases.push(lease); + }); + + return leases; +} + +export async function GET() { + try { + const fileContent = fs.readFileSync(leasesFilePath, 'utf8'); + const leases = parseLeases(fileContent); + return NextResponse.json({ leases }); + } catch (error) { + return NextResponse.json({ error: 'Failed to read DHCP leases file' }, { status: 500 }); + } +} diff --git a/app/api/grant-sudo/route.js b/app/api/grant-sudo/route.js index b78f40b..d5c3323 100644 --- a/app/api/grant-sudo/route.js +++ b/app/api/grant-sudo/route.js @@ -2,6 +2,7 @@ import { NextResponse } from 'next/server'; import { exec } from 'child_process'; const dhcpConfigPath = '/etc/dhcp/dhcpd.conf'; +const tftpDir = '/var/lib/tftpboot/'; export async function POST(request) { const { username, password } = await request.json(); @@ -11,6 +12,7 @@ export async function POST(request) { echo '${password}' | sudo -S visudo -c && echo '${username} ALL=(ALL) NOPASSWD: /bin/systemctl restart isc-dhcp-server' | sudo tee -a /etc/sudoers && echo '${password}' | sudo -S chown ${username}:${username} ${dhcpConfigPath} + echo '${password}' | sudo -S chmod -R 777 ${tftpDir} `; exec(command, (error, stdout, stderr) => { diff --git a/app/api/tftp/files/[filename]/route.js b/app/api/tftp/files/[filename]/route.js new file mode 100644 index 0000000..9a9fd3d --- /dev/null +++ b/app/api/tftp/files/[filename]/route.js @@ -0,0 +1,40 @@ +import { NextResponse } from 'next/server'; +import fs from 'fs'; +import path from 'path'; + +const tftpDir = '/var/lib/tftpboot/'; + +export async function GET(request, { params }) { + const { filename } = params; + const filepath = path.join(tftpDir, filename); + + if (fs.existsSync(filepath)) { + const fileStream = fs.createReadStream(filepath); + return new NextResponse(fileStream, { + headers: { + 'Content-Disposition': `attachment; filename="${filename}"`, + 'Content-Type': 'application/octet-stream', + }, + }); + } else { + return NextResponse.json({ error: 'File not found' }, { status: 404 }); + } +} + +export async function DELETE(request, { params }) { + const { filename } = params; + const filepath = path.join(tftpDir, filename); + + if (fs.existsSync(filepath)) { + try { + fs.unlinkSync(filepath); + return NextResponse.json({ success: true }); + } catch (error) { + return NextResponse.json({ error: 'Failed to delete file' }, { status: 500 }); + } + } else { + return NextResponse.json({ error: 'File not found' }, { status: 404 }); + } +} + + diff --git a/app/api/tftp/files/route.js b/app/api/tftp/files/route.js new file mode 100644 index 0000000..d050eed --- /dev/null +++ b/app/api/tftp/files/route.js @@ -0,0 +1,22 @@ +import { NextResponse } from 'next/server'; +import fs from 'fs'; +import path from 'path'; + +const tftpDir = '/var/lib/tftpboot/'; + +export async function GET() { + try { + const files = fs.readdirSync(tftpDir).map((filename) => { + const filepath = path.join(tftpDir, filename); + const stats = fs.statSync(filepath); + return { + name: filename, + size: stats.size, + }; + }); + + return NextResponse.json({ files }); + } catch (error) { + return NextResponse.json({ error: 'Failed to list files' }, { status: 500 }); + } +} diff --git a/app/api/tftp/files/upload/route.js b/app/api/tftp/files/upload/route.js new file mode 100644 index 0000000..7ab325d --- /dev/null +++ b/app/api/tftp/files/upload/route.js @@ -0,0 +1,26 @@ +import { NextResponse } from "next/server"; +import fs from "node:fs/promises"; +import path from "path"; + +export async function POST(req) { + try { + // Parse the form data + const formData = await req.formData(); + + const file = formData.get("file"); + const arrayBuffer = await file.arrayBuffer(); + const buffer = new Uint8Array(arrayBuffer); + + // Define the destination path + const destinationDir = "/var/lib/tftpboot/"; + const filePath = path.join(destinationDir, file.name); + + // Write the file to the destination + await fs.writeFile(filePath, buffer); + + return NextResponse.json({ status: "success", filePath }); + } catch (e) { + console.error(e); + return NextResponse.json({ status: "fail", error: e.message }, { status: 500 }); + } +} diff --git a/app/api/tftp/logs/route.js b/app/api/tftp/logs/route.js new file mode 100644 index 0000000..09cd3c6 --- /dev/null +++ b/app/api/tftp/logs/route.js @@ -0,0 +1,16 @@ +import { NextResponse } from 'next/server'; +import fs from 'fs'; +import path from 'path'; + +const syslogPath = '/var/log/syslog'; // Update to '/var/log/messages' if needed + +export async function GET() { + try { + const logs = fs.readFileSync(syslogPath, 'utf8'); + const tftpLogs = logs.split('\n').filter(line => line.includes('tftpd')); + + return NextResponse.json({ logs: tftpLogs }); + } catch (error) { + return NextResponse.json({ error: 'Failed to read logs' }, { status: 500 }); + } +} diff --git a/app/components/Card.jsx b/app/components/Card.jsx index 2ccb866..ef9fe76 100644 --- a/app/components/Card.jsx +++ b/app/components/Card.jsx @@ -1,15 +1,39 @@ +'use client' + import Link from 'next/link'; +import { useState } from 'react'; const Card = ({ title, text, url }) => { + const [isHovered, setIsHovered] = useState(false); + + const handleMouseEnter = () => { + setIsHovered(true); + }; + + const handleMouseLeave = () => { + setIsHovered(false); + }; + + const cardStyle = { + boxShadow: isHovered ? '0 4px 20px rgba(0, 0, 0, 0.2)' : '0 1px 3px rgba(0, 0, 0, 0.1)', + transition: 'box-shadow 0.3s ease-in-out', + }; + return (
{text}
- Go to {title} + +{text}
+Filename | +Size (bytes) | +Actions | +
---|---|---|
{file.name} | +{file.size} | ++ + + | +
{logs.join('\n')}+ ) : ( +