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:
113
resources/js/Pages/Wiki/Index.jsx
Normal file
113
resources/js/Pages/Wiki/Index.jsx
Normal file
@@ -0,0 +1,113 @@
|
||||
import AppLayout from '@/Layouts/AppLayout';
|
||||
import { Head, router } from '@inertiajs/react';
|
||||
import { BookOpen, Plus, FileText, Folder, Upload, Search } from 'lucide-react';
|
||||
import { GlassCard } from '@/Components/ui/GlassCard';
|
||||
import { Button } from '@/Components/ui/Button';
|
||||
import { motion } from 'framer-motion';
|
||||
import { timeAgo } from '@/lib/utils';
|
||||
import { useState } from 'react';
|
||||
|
||||
export default function WikiIndex({ spaces, recent_pages, files }) {
|
||||
const [search, setSearch] = useState('');
|
||||
|
||||
const filtered = (recent_pages ?? []).filter(p =>
|
||||
p.title.toLowerCase().includes(search.toLowerCase())
|
||||
);
|
||||
|
||||
return (
|
||||
<AppLayout title="Wiki">
|
||||
<Head title="Wiki & Documentation" />
|
||||
<div className="space-y-5">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 className="text-xl font-bold text-white">Wiki & Documents</h2>
|
||||
<p className="text-sm text-white/40">Centralized documentation, runbooks, and files</p>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button variant="secondary" icon={Upload}>Upload File</Button>
|
||||
<a href="/wiki/pages/create"><Button icon={Plus}>New Page</Button></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Search */}
|
||||
<div className="relative">
|
||||
<Search className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-white/30" />
|
||||
<input
|
||||
value={search}
|
||||
onChange={e => setSearch(e.target.value)}
|
||||
placeholder="Search pages, documents..."
|
||||
className="w-full pl-12 pr-4 py-3 bg-white/5 border border-white/10 rounded-xl text-sm text-white placeholder:text-white/20 focus:outline-none focus:border-cyan-500/50 transition-all"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-4">
|
||||
{/* Spaces */}
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-sm font-semibold text-white">Spaces</h3>
|
||||
<Button size="sm" variant="ghost" icon={Plus}>New Space</Button>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
{(spaces ?? []).map(space => (
|
||||
<a key={space.id} href={`/wiki/spaces/${space.slug}`}>
|
||||
<GlassCard className="p-4 cursor-pointer" hover>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-9 h-9 rounded-lg flex items-center justify-center text-lg"
|
||||
style={{ backgroundColor: `${space.color}20`, color: space.color }}>
|
||||
<Folder className="w-5 h-5" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-white">{space.name}</p>
|
||||
<p className="text-xs text-white/30">{space.pages_count ?? 0} pages</p>
|
||||
</div>
|
||||
</div>
|
||||
</GlassCard>
|
||||
</a>
|
||||
))}
|
||||
{(!spaces || spaces.length === 0) && (
|
||||
<GlassCard className="p-6 text-center">
|
||||
<BookOpen className="w-8 h-8 text-white/20 mx-auto mb-2" />
|
||||
<p className="text-sm text-white/30">No spaces yet</p>
|
||||
</GlassCard>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Recent Pages */}
|
||||
<div className="lg:col-span-2 space-y-3">
|
||||
<h3 className="text-sm font-semibold text-white">
|
||||
{search ? `Search: "${search}"` : 'Recent Pages'}
|
||||
</h3>
|
||||
<div className="space-y-2">
|
||||
{filtered.map(page => (
|
||||
<a key={page.id} href={`/wiki/pages/${page.id}`}>
|
||||
<GlassCard className="p-4 cursor-pointer" hover>
|
||||
<div className="flex items-start gap-3">
|
||||
<FileText className="w-4 h-4 text-white/30 mt-0.5 shrink-0" />
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-medium text-white">{page.title}</p>
|
||||
<div className="flex items-center gap-3 mt-1">
|
||||
<p className="text-xs text-white/30">{page.space?.name}</p>
|
||||
<span className="text-white/10">·</span>
|
||||
<p className="text-xs text-white/30">Updated {timeAgo(page.updated_at)}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</GlassCard>
|
||||
</a>
|
||||
))}
|
||||
{filtered.length === 0 && (
|
||||
<GlassCard className="p-8 text-center">
|
||||
<FileText className="w-8 h-8 text-white/20 mx-auto mb-2" />
|
||||
<p className="text-sm text-white/30">
|
||||
{search ? 'No pages match your search' : 'No pages yet. Create your first page!'}
|
||||
</p>
|
||||
</GlassCard>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AppLayout>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user