feat: initialize Infra Manager with React, Tailwind CSS, and Inertia.js
- Added main application entry point (app.jsx) and bootstrap file for React setup. - Created Wiki index page (Index.jsx) for documentation and file management. - Implemented utility functions for formatting and status handling in utils.js. - Set up Blade template (app.blade.php) for Inertia.js integration. - Defined API routes for metrics and terminal access in api.php. - Established console commands for scheduled tasks in console.php. - Created web routes for various application features including authentication, hypervisors, containers, cloud accounts, security, backups, and wiki management in web.php. - Developed setup scripts for Windows (setup.ps1) and Linux (setup.sh) environments. - Configured Tailwind CSS for styling with custom themes and animations in tailwind.config.js. - Set up Vite for asset bundling and development server in vite.config.js.
This commit is contained in:
146
resources/js/Pages/Admin/Ldap.jsx
Normal file
146
resources/js/Pages/Admin/Ldap.jsx
Normal file
@@ -0,0 +1,146 @@
|
||||
import AppLayout from '@/Layouts/AppLayout';
|
||||
import { Head, useForm, router } from '@inertiajs/react';
|
||||
import { useState } from 'react';
|
||||
import { Globe, CheckCircle, XCircle, RefreshCw } from 'lucide-react';
|
||||
import { GlassCard } from '@/Components/ui/GlassCard';
|
||||
import { Button } from '@/Components/ui/Button';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
function Input({ className, ...props }) {
|
||||
return <input className={cn('w-full px-3 py-2 bg-white/5 border border-white/10 rounded-lg text-sm text-white placeholder:text-white/20 focus:outline-none focus:border-cyan-500/60 transition-all', className)} {...props} />;
|
||||
}
|
||||
function Field({ label, desc, children }) {
|
||||
return (
|
||||
<div className="flex items-start justify-between gap-8 py-4 border-b border-white/[0.06] last:border-0">
|
||||
<div className="flex-1">
|
||||
<p className="text-sm font-medium text-white">{label}</p>
|
||||
{desc && <p className="text-xs text-white/40 mt-0.5">{desc}</p>}
|
||||
</div>
|
||||
<div className="w-72 shrink-0">{children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function LdapConfig({ config }) {
|
||||
const { data, setData, put, processing } = useForm({
|
||||
host: config?.host ?? '',
|
||||
port: config?.port ?? 389,
|
||||
use_ssl: config?.use_ssl ?? false,
|
||||
use_tls: config?.use_tls ?? false,
|
||||
base_dn: config?.base_dn ?? '',
|
||||
bind_dn: config?.bind_dn ?? '',
|
||||
bind_password: '',
|
||||
user_search_base: config?.user_search_base ?? '',
|
||||
user_filter: config?.user_filter ?? '(sAMAccountName={username})',
|
||||
admin_group_dn: config?.admin_group_dn ?? '',
|
||||
is_active: config?.is_active ?? false,
|
||||
});
|
||||
const [testResult, setTestResult] = useState(null);
|
||||
const [testing, setTesting] = useState(false);
|
||||
const [syncing, setSyncing] = useState(false);
|
||||
|
||||
const test = async () => {
|
||||
setTesting(true);
|
||||
setTestResult(null);
|
||||
try {
|
||||
const res = await fetch('/admin/ldap/test', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': document.querySelector('meta[name=csrf-token]').content },
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
setTestResult(await res.json());
|
||||
} catch (e) {
|
||||
setTestResult({ success: false, message: e.message });
|
||||
} finally {
|
||||
setTesting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const sync = () => {
|
||||
setSyncing(true);
|
||||
router.post('/admin/ldap/sync', {}, { onFinish: () => setSyncing(false) });
|
||||
};
|
||||
|
||||
return (
|
||||
<AppLayout title="LDAP Configuration">
|
||||
<Head title="LDAP" />
|
||||
<div className="max-w-3xl space-y-5">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 className="text-xl font-bold text-white">LDAP / Active Directory</h2>
|
||||
<p className="text-sm text-white/40">Integrate with your directory service for SSO</p>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button variant="secondary" icon={RefreshCw} loading={syncing} onClick={sync}>Sync Users</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form onSubmit={e => { e.preventDefault(); put('/admin/ldap'); }}>
|
||||
<div className="space-y-4">
|
||||
<GlassCard className="p-5">
|
||||
<h3 className="text-sm font-semibold text-white/60 uppercase tracking-wider mb-1">Connection</h3>
|
||||
<Field label="Enable LDAP" desc="Allow users to authenticate via LDAP">
|
||||
<label className="flex items-center gap-3 cursor-pointer">
|
||||
<input type="checkbox" checked={data.is_active} onChange={e => setData('is_active', e.target.checked)}
|
||||
className="w-4 h-4 rounded border-white/20 bg-white/5 text-cyan-500 focus:ring-0" />
|
||||
<span className="text-sm text-white/70">Enabled</span>
|
||||
</label>
|
||||
</Field>
|
||||
<Field label="LDAP Host">
|
||||
<Input value={data.host} onChange={e => setData('host', e.target.value)} placeholder="ldap.company.com" />
|
||||
</Field>
|
||||
<Field label="Port">
|
||||
<Input type="number" value={data.port} onChange={e => setData('port', parseInt(e.target.value))} />
|
||||
</Field>
|
||||
<Field label="Encryption">
|
||||
<div className="flex gap-3">
|
||||
{[['use_ssl', 'LDAPS (SSL)'], ['use_tls', 'STARTTLS']].map(([k, l]) => (
|
||||
<label key={k} className="flex items-center gap-2 cursor-pointer">
|
||||
<input type="checkbox" checked={data[k]} onChange={e => setData(k, e.target.checked)}
|
||||
className="w-3.5 h-3.5 rounded border-white/20 bg-white/5 text-cyan-500 focus:ring-0" />
|
||||
<span className="text-xs text-white/60">{l}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</Field>
|
||||
</GlassCard>
|
||||
|
||||
<GlassCard className="p-5">
|
||||
<h3 className="text-sm font-semibold text-white/60 uppercase tracking-wider mb-1">Bind / Authentication</h3>
|
||||
<Field label="Base DN"><Input value={data.base_dn} onChange={e => setData('base_dn', e.target.value)} placeholder="DC=company,DC=com" /></Field>
|
||||
<Field label="Bind DN"><Input value={data.bind_dn} onChange={e => setData('bind_dn', e.target.value)} placeholder="CN=ldap-reader,CN=Users,DC=company,DC=com" /></Field>
|
||||
<Field label="Bind Password"><Input type="password" value={data.bind_password} onChange={e => setData('bind_password', e.target.value)} placeholder="Leave blank to keep existing" /></Field>
|
||||
</GlassCard>
|
||||
|
||||
<GlassCard className="p-5">
|
||||
<h3 className="text-sm font-semibold text-white/60 uppercase tracking-wider mb-1">User Search</h3>
|
||||
<Field label="User Search Base"><Input value={data.user_search_base} onChange={e => setData('user_search_base', e.target.value)} placeholder="CN=Users,DC=company,DC=com" /></Field>
|
||||
<Field label="User Filter" desc="Use {username} as placeholder">
|
||||
<Input value={data.user_filter} onChange={e => setData('user_filter', e.target.value)} />
|
||||
</Field>
|
||||
<Field label="Admin Group DN" desc="Members of this group get admin role">
|
||||
<Input value={data.admin_group_dn} onChange={e => setData('admin_group_dn', e.target.value)} placeholder="CN=Infra-Admins,CN=Groups,..." />
|
||||
</Field>
|
||||
</GlassCard>
|
||||
|
||||
{testResult && (
|
||||
<motion.div initial={{ opacity: 0, y: -10 }} animate={{ opacity: 1, y: 0 }}
|
||||
className={cn('flex items-center gap-3 p-3 rounded-xl border text-sm',
|
||||
testResult.success ? 'bg-green-500/10 border-green-500/30 text-green-300' : 'bg-red-500/10 border-red-500/30 text-red-300'
|
||||
)}>
|
||||
{testResult.success ? <CheckCircle className="w-4 h-4" /> : <XCircle className="w-4 h-4" />}
|
||||
{testResult.message}
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
<div className="flex items-center justify-between pt-2">
|
||||
<Button type="button" variant="secondary" loading={testing} onClick={test}>Test Connection</Button>
|
||||
<Button type="submit" loading={processing}>Save Configuration</Button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</AppLayout>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user