Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions frontend/app/(dashboard)/notifications-live/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
'use client';

import { useEffect, useRef, useState } from 'react';
import { Bell, Wifi, WifiOff } from 'lucide-react';

interface Notification { id: string; message: string; type: 'info' | 'warning' | 'success'; timestamp: string }

export default function NotificationsRealtimePage() {
const [connected, setConnected] = useState(false);
const [notifications, setNotifications] = useState<Notification[]>([
{ id:'1', message:'Asset AST-042 has been checked out by Alice M.', type:'info', timestamp: new Date().toISOString() },
{ id:'2', message:'Maintenance scheduled for Server Rack A on Jan 25', type:'warning', timestamp: new Date().toISOString() },
]);
const wsRef = useRef<WebSocket | null>(null);

useEffect(() => {
const wsUrl = (process.env.NEXT_PUBLIC_WS_URL ?? 'ws://localhost:3001') + '/notifications';
try {
const ws = new WebSocket(wsUrl);
wsRef.current = ws;
ws.onopen = () => setConnected(true);
ws.onclose = () => setConnected(false);
ws.onmessage = (e) => {
try {
const data = JSON.parse(e.data as string) as Notification;
setNotifications(prev => [data, ...prev].slice(0, 50));
} catch { /* ignore malformed */ }
};
} catch { /* ws not available in test env */ }
return () => { wsRef.current?.close(); };
}, []);

const TYPE_STYLE: Record<string, string> = { info:'border-blue-200 bg-blue-50', warning:'border-yellow-200 bg-yellow-50', success:'border-green-200 bg-green-50' };

return (
<div>
<div className="flex items-center justify-between mb-6">
<div><h1 className="text-2xl font-bold text-gray-900">Real-time Notifications</h1><p className="text-sm text-gray-500 mt-1">Live updates via WebSocket</p></div>
<span className={ lex items-center gap-1.5 text-sm }>
{connected ? <Wifi size={16}/> : <WifiOff size={16}/>}{connected ? 'Connected' : 'Disconnected'}
</span>
</div>
<div className="space-y-3">
{notifications.length === 0
? <div className="bg-white rounded-xl border border-gray-200 p-10 text-center text-sm text-gray-400"><Bell size={24} className="mx-auto mb-2 text-gray-300"/>No notifications yet</div>
: notifications.map(n => (
<div key={n.id} className={ounded-xl border p-4 }>
<p className="text-sm text-gray-800">{n.message}</p>
<p className="text-xs text-gray-400 mt-1">{new Date(n.timestamp).toLocaleString()}</p>
</div>
))}
</div>
</div>
);
}
Expand Down
56 changes: 56 additions & 0 deletions frontend/app/(dashboard)/rbac/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
'use client';

import { useState } from 'react';
import { Plus, Shield } from 'lucide-react';
import { Button } from '@/components/ui/button';

interface Role { id: string; name: string; department: string; permissions: string[] }

const MOCK: Role[] = [
{ id:'1', name:'Admin', department:'All', permissions:['create','read','update','delete','manage_users'] },
{ id:'2', name:'Manager', department:'Engineering', permissions:['create','read','update'] },
{ id:'3', name:'Viewer', department:'Finance', permissions:['read'] },
];

const ALL_PERMS = ['create','read','update','delete','manage_users'];

export default function RBACPage() {
const [roles] = useState<Role[]>(MOCK);
const [selected, setSelected] = useState<Role | null>(null);

return (
<div>
<div className="flex items-center justify-between mb-6">
<div><h1 className="text-2xl font-bold text-gray-900">Roles &amp; Permissions</h1><p className="text-sm text-gray-500 mt-1">Manage roles, permissions, and department access</p></div>
<Button><Plus size={16} className="mr-1.5" />New Role</Button>
</div>
<div className="grid grid-cols-3 gap-4">
<div className="col-span-1 bg-white rounded-xl border border-gray-200 overflow-hidden">
<div className="px-4 py-3 border-b border-gray-100 text-sm font-semibold text-gray-700">Roles</div>
{roles.map(r => (
<button key={r.id} onClick={() => setSelected(r)} className={w-full text-left px-4 py-3 flex items-center gap-2 hover:bg-gray-50 border-b border-gray-100 }>
<Shield size={14} className="text-gray-400" />
<div><p className="text-sm font-medium text-gray-900">{r.name}</p><p className="text-xs text-gray-500">{r.department}</p></div>
</button>
))}
</div>
<div className="col-span-2 bg-white rounded-xl border border-gray-200 p-5">
{selected ? (
<>
<h2 className="text-lg font-semibold text-gray-900 mb-4">{selected.name} — Permissions</h2>
<div className="grid grid-cols-2 gap-3">
{ALL_PERMS.map(perm => (
<label key={perm} className="flex items-center gap-2 cursor-pointer">
<input type="checkbox" defaultChecked={selected.permissions.includes(perm)} className="rounded" />
<span className="text-sm capitalize text-gray-700">{perm.replace('_',' ')}</span>
</label>
))}
</div>
<Button className="mt-6">Save Changes</Button>
</>
) : <p className="text-sm text-gray-400">Select a role to manage permissions</p>}
</div>
</div>
</div>
);
}
Loading