parent
2280abae90
commit
863560921e
@ -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 });
|
||||
}
|
||||
}
|
@ -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 });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 });
|
||||
}
|
||||
}
|
@ -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 });
|
||||
}
|
||||
}
|
@ -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 });
|
||||
}
|
||||
}
|
@ -1,16 +1,37 @@
|
||||
import { parseLeases } from '@/app/lib/parseLeases';
|
||||
'use client';
|
||||
|
||||
export const metadata = {
|
||||
title: 'DHCP Leases',
|
||||
description: 'View current DHCP leases',
|
||||
};
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useNotification } from '../../context/NotificationContext';
|
||||
|
||||
export default function LeasesPage() {
|
||||
const leases = parseLeases();
|
||||
const [leases, setLeases] = useState([]);
|
||||
const { showNotification } = useNotification();
|
||||
|
||||
const fetchLeases = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/dhcp/leases'); // Replace with your actual API endpoint for leases
|
||||
const data = await response.json();
|
||||
if (response.ok) {
|
||||
setLeases(data.leases);
|
||||
showNotification('Leases refreshed successfully.', 'success');
|
||||
} else {
|
||||
showNotification(data.error, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
showNotification('Failed to fetch leases.', 'error');
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchLeases();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="container mt-5">
|
||||
<h1 className="mb-4">Current DHCP Leases</h1>
|
||||
<button className="btn btn-secondary mb-3" onClick={fetchLeases}>
|
||||
Refresh Leases
|
||||
</button>
|
||||
<table className="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
@ -0,0 +1,9 @@
|
||||
|
||||
|
||||
.hover-shadow {
|
||||
transition: box-shadow 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.hover-shadow:hover {
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
|
||||
}
|
@ -0,0 +1,124 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useNotification } from '@/app/context/NotificationContext';
|
||||
|
||||
export default function TFTPPage() {
|
||||
const [files, setFiles] = useState([]);
|
||||
const [selectedFile, setSelectedFile] = useState(null);
|
||||
const { showNotification } = useNotification();
|
||||
|
||||
const fetchFiles = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/tftp/files');
|
||||
const data = await response.json();
|
||||
if (response.ok) {
|
||||
setFiles(data.files);
|
||||
showNotification('File list refreshed successfully.', 'success');
|
||||
} else {
|
||||
showNotification(data.error, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
showNotification('Failed to fetch files.', 'error');
|
||||
}
|
||||
};
|
||||
|
||||
const handleDownload = (filename) => {
|
||||
window.location.href = `/api/tftp/files/${filename}`;
|
||||
};
|
||||
|
||||
const handleDelete = async (filename) => {
|
||||
if (confirm(`Are you sure you want to delete ${filename}?`)) {
|
||||
try {
|
||||
const response = await fetch(`/api/tftp/files/${filename}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
const data = await response.json();
|
||||
if (response.ok) {
|
||||
showNotification('File deleted successfully.', 'success');
|
||||
fetchFiles();
|
||||
} else {
|
||||
showNotification(data.error, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
showNotification('Failed to delete file.', 'error');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleUpload = async (event) => {
|
||||
event.preventDefault();
|
||||
if (!selectedFile) return;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('file', selectedFile);
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/tftp/files/upload', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
});
|
||||
const data = await response.json();
|
||||
if (response.ok) {
|
||||
showNotification('File uploaded successfully.', 'success');
|
||||
fetchFiles();
|
||||
} else {
|
||||
showNotification(data.error, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
showNotification('Failed to upload file.', 'error');
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchFiles();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="container mt-5">
|
||||
<h1 className="mb-4">TFTP Server Files</h1>
|
||||
|
||||
<div className="mb-3">
|
||||
<label htmlFor="fileUpload" className="form-label">Upload New File</label>
|
||||
<input
|
||||
type="file"
|
||||
className="form-control"
|
||||
id="fileUpload"
|
||||
onChange={(e) => setSelectedFile(e.target.files[0])}
|
||||
/>
|
||||
<button className="btn btn-primary mt-2" onClick={handleUpload}>
|
||||
Upload File
|
||||
</button>
|
||||
<button className="btn btn-secondary mt-2 ml-2" onClick={fetchFiles}>
|
||||
Refresh
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<table className="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Filename</th>
|
||||
<th scope="col">Size (bytes)</th>
|
||||
<th scope="col">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{files.map((file) => (
|
||||
<tr key={file.name}>
|
||||
<td>{file.name}</td>
|
||||
<td>{file.size}</td>
|
||||
<td>
|
||||
<button className="btn btn-success btn-sm mr-2" onClick={() => handleDownload(file.name)}>
|
||||
Download
|
||||
</button>
|
||||
<button className="btn btn-danger btn-sm" onClick={() => handleDelete(file.name)}>
|
||||
Delete
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useNotification } from '../../context/NotificationContext';
|
||||
|
||||
export default function TFTPLogsPage() {
|
||||
const [logs, setLogs] = useState([]);
|
||||
const [error, setError] = useState('');
|
||||
const { showNotification } = useNotification();
|
||||
|
||||
const fetchLogs = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/tftp/logs');
|
||||
const data = await response.json();
|
||||
if (response.ok) {
|
||||
setLogs(data.logs);
|
||||
setError('');
|
||||
showNotification('Logs refreshed successfully.', 'success');
|
||||
} else {
|
||||
setError(data.error);
|
||||
showNotification('Failed to refresh logs.', 'error');
|
||||
}
|
||||
} catch (err) {
|
||||
setError('Failed to fetch logs');
|
||||
showNotification('Failed to fetch logs.', 'error');
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchLogs();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="container mt-5">
|
||||
<h1 className="mb-4">TFTP Server Logs</h1>
|
||||
<button className="btn btn-secondary mb-3" onClick={fetchLogs}>
|
||||
Refresh Logs
|
||||
</button>
|
||||
{error && <div className="alert alert-danger">{error}</div>}
|
||||
<div className="log-container" style={{ maxHeight: '500px', overflowY: 'auto' }}>
|
||||
{logs.length > 0 ? (
|
||||
<pre>{logs.join('\n')}</pre>
|
||||
) : (
|
||||
<div className="alert alert-info">No logs available or logs are not accessible.</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
Loading…
Reference in new issue