new changes

This commit is contained in:
Niranjan
2026-04-07 05:05:28 +05:30
parent 7c070224bd
commit a18bba15f2
29975 changed files with 3247495 additions and 2761 deletions

View File

@@ -1,6 +1,7 @@
import { useEffect, useState } from 'react'
import Modal from 'react-bootstrap/Modal'
import { apiRequest, updateFtpPassword } from '../api/client'
import { Plus, Trash2, Key } from 'lucide-react'
import { PageHeader, AdminButton, AdminAlert, AdminTable, EmptyState } from '../components/admin'
interface FtpAccount {
id: number
@@ -84,174 +85,159 @@ export function FtpPage() {
.catch((err) => setError(err.message))
}
if (loading) return <div className="text-gray-500">Loading...</div>
if (error) return <div className="p-4 rounded bg-red-100 text-red-700">{error}</div>
if (loading) {
return (
<>
<PageHeader title="FTP" />
<div className="text-center py-5 text-muted">Loading</div>
</>
)
}
if (error) {
return (
<>
<PageHeader title="FTP" />
<AdminAlert variant="danger">{error}</AdminAlert>
</>
)
}
return (
<div>
<div className="flex justify-between items-center mb-4">
<h1 className="text-2xl font-bold text-gray-800 dark:text-white">FTP</h1>
<button
onClick={() => setShowCreate(true)}
className="flex items-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg font-medium"
>
<Plus className="w-4 h-4" />
Add FTP
</button>
<>
<PageHeader
title="FTP"
actions={
<AdminButton onClick={() => setShowCreate(true)}>
<i className="ti ti-plus me-1" />
Add FTP
</AdminButton>
}
/>
<div className="alert alert-secondary" role="note">
FTP accounts use Pure-FTPd (pure-pw). Path must be under www root. Install:{' '}
<code>apt install pure-ftpd pure-ftpd-common</code>
</div>
<div className="mb-4 p-3 rounded-lg bg-gray-100 dark:bg-gray-700/50 text-sm text-gray-600 dark:text-gray-400">
FTP accounts use Pure-FTPd (pure-pw). Path must be under www root. Install: <code className="font-mono">apt install pure-ftpd pure-ftpd-common</code>
</div>
<Modal show={showCreate} onHide={() => setShowCreate(false)} centered>
<Modal.Header closeButton>
<Modal.Title>Create FTP Account</Modal.Title>
</Modal.Header>
<form onSubmit={handleCreate}>
<Modal.Body>
{creatingError ? <AdminAlert variant="danger">{creatingError}</AdminAlert> : null}
<div className="mb-3">
<label className="form-label">Username</label>
<input
name="name"
type="text"
placeholder="ftpuser"
className="form-control"
required
/>
</div>
<div className="mb-3">
<label className="form-label">Password</label>
<input name="password" type="password" className="form-control" required />
</div>
<div className="mb-3">
<label className="form-label">Path</label>
<input name="path" type="text" placeholder="/www/wwwroot" className="form-control" required />
</div>
<div className="mb-0">
<label className="form-label">Note (optional)</label>
<input name="ps" type="text" placeholder="My FTP" className="form-control" />
</div>
</Modal.Body>
<Modal.Footer>
<button type="button" className="btn btn-light" onClick={() => setShowCreate(false)}>
Cancel
</button>
<button type="submit" disabled={creating} className="btn btn-primary">
{creating ? 'Creating…' : 'Create'}
</button>
</Modal.Footer>
</form>
</Modal>
{showCreate && (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-xl p-6 w-full max-w-md">
<h2 className="text-xl font-bold mb-4 text-gray-800 dark:text-white">Create FTP Account</h2>
<form onSubmit={handleCreate} className="space-y-4">
{creatingError && (
<div className="p-2 rounded bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-300 text-sm">
{creatingError}
</div>
)}
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Username</label>
<input
name="name"
type="text"
placeholder="ftpuser"
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700"
required
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Password</label>
<input
name="password"
type="password"
placeholder="••••••••"
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700"
required
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Path</label>
<input
name="path"
type="text"
placeholder="/www/wwwroot"
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700"
required
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Note (optional)</label>
<input
name="ps"
type="text"
placeholder="My FTP"
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700"
/>
</div>
<div className="flex gap-2 justify-end pt-2">
<button
type="button"
onClick={() => setShowCreate(false)}
className="px-4 py-2 text-gray-600 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg"
>
Cancel
</button>
<button
type="submit"
disabled={creating}
className="px-4 py-2 bg-blue-600 hover:bg-blue-700 disabled:bg-blue-400 text-white rounded-lg font-medium"
>
{creating ? 'Creating...' : 'Create'}
</button>
</div>
</form>
</div>
</div>
)}
<div className="bg-white dark:bg-gray-800 rounded-lg shadow overflow-hidden">
<table className="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
<thead className="bg-gray-50 dark:bg-gray-700">
<tr>
<th className="px-4 py-2 text-left text-sm font-medium text-gray-700 dark:text-gray-300">Name</th>
<th className="px-4 py-2 text-left text-sm font-medium text-gray-700 dark:text-gray-300">Path</th>
<th className="px-4 py-2 text-left text-sm font-medium text-gray-700 dark:text-gray-300">Note</th>
<th className="px-4 py-2 text-right text-sm font-medium text-gray-700 dark:text-gray-300">Actions</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-200 dark:divide-gray-700">
{accounts.length === 0 ? (
<div className="card shadow-sm border-0">
<div className="card-body p-0">
<AdminTable>
<thead className="table-light">
<tr>
<td colSpan={4} className="px-4 py-8 text-center text-gray-500">
No FTP accounts. Click "Add FTP" to create one.
</td>
<th>Name</th>
<th>Path</th>
<th>Note</th>
<th className="text-end">Actions</th>
</tr>
) : (
accounts.map((a) => (
<tr key={a.id}>
<td className="px-4 py-2 text-gray-900 dark:text-white">{a.name}</td>
<td className="px-4 py-2 text-gray-600 dark:text-gray-400">{a.path}</td>
<td className="px-4 py-2 text-gray-600 dark:text-gray-400">{a.ps || '-'}</td>
<td className="px-4 py-2 text-right">
<span className="flex gap-1 justify-end">
<button
onClick={() => setChangePwId(a.id)}
className="p-2 text-amber-600 hover:bg-amber-50 dark:hover:bg-amber-900/20 rounded"
title="Change password"
>
<Key className="w-4 h-4" />
</button>
<button
onClick={() => handleDelete(a.id, a.name)}
className="p-2 text-red-600 hover:bg-red-50 dark:hover:bg-red-900/20 rounded"
title="Delete"
>
<Trash2 className="w-4 h-4" />
</button>
</span>
</thead>
<tbody>
{accounts.length === 0 ? (
<tr>
<td colSpan={4} className="p-0">
<EmptyState title="No FTP accounts" description='Click "Add FTP" to create one.' />
</td>
</tr>
))
)}
</tbody>
</table>
) : (
accounts.map((a) => (
<tr key={a.id}>
<td className="align-middle">{a.name}</td>
<td className="align-middle">{a.path}</td>
<td className="align-middle text-muted">{a.ps || '—'}</td>
<td className="align-middle text-end">
<button
type="button"
onClick={() => setChangePwId(a.id)}
className="btn btn-sm btn-outline-warning me-1"
title="Change password"
>
<i className="ti ti-key" />
</button>
<button
type="button"
onClick={() => handleDelete(a.id, a.name)}
className="btn btn-sm btn-outline-danger"
title="Delete"
>
<i className="ti ti-trash" />
</button>
</td>
</tr>
))
)}
</tbody>
</AdminTable>
</div>
</div>
{changePwId && (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-xl p-6 w-full max-w-md">
<h2 className="text-xl font-bold mb-4 text-gray-800 dark:text-white">Change FTP Password</h2>
<form onSubmit={(e) => handleChangePassword(e, changePwId)} className="space-y-4">
{pwError && (
<div className="p-2 rounded bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-300 text-sm">
{pwError}
</div>
)}
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">New Password</label>
<input name="new_password" type="password" minLength={6} className="w-full px-4 py-2 border rounded-lg bg-white dark:bg-gray-700" required />
<Modal show={changePwId != null} onHide={() => setChangePwId(null)} centered>
<Modal.Header closeButton>
<Modal.Title>Change FTP Password</Modal.Title>
</Modal.Header>
{changePwId != null ? (
<form onSubmit={(e) => handleChangePassword(e, changePwId)}>
<Modal.Body>
{pwError ? <AdminAlert variant="danger">{pwError}</AdminAlert> : null}
<div className="mb-3">
<label className="form-label">New Password</label>
<input name="new_password" type="password" minLength={6} className="form-control" required />
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Confirm Password</label>
<input name="confirm_password" type="password" minLength={6} className="w-full px-4 py-2 border rounded-lg bg-white dark:bg-gray-700" required />
<div className="mb-0">
<label className="form-label">Confirm Password</label>
<input name="confirm_password" type="password" minLength={6} className="form-control" required />
</div>
<div className="flex gap-2 justify-end">
<button type="button" onClick={() => setChangePwId(null)} className="px-4 py-2 text-gray-600 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg">
Cancel
</button>
<button type="submit" className="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg">
Update
</button>
</div>
</form>
</div>
</div>
)}
</div>
</Modal.Body>
<Modal.Footer>
<button type="button" className="btn btn-light" onClick={() => setChangePwId(null)}>
Cancel
</button>
<button type="submit" className="btn btn-primary">
Update
</button>
</Modal.Footer>
</form>
) : null}
</Modal>
</>
)
}