fix: resolve frontend routing, API mismatches, and missing UI components
- Add servers list page and missing routes (servers, settings redirect, account security)
- Fix members page .map error (API returns { data } wrapper, not flat array)
- Fix auth store fetchUser expecting flat User but API returns { user } wrapper
- Add node token display dialog after creation
- Add allocation management UI to node detail page
- Add account security page with password change
- Add change-password API endpoint
- Add node servers and stats API endpoints
- Fix config save using PATCH instead of PUT, add api.put method
- Fix audit logs field name mismatch (userName vs username)
- Replace admin nodes page to avoid orgId dependency
- Remove duplicate sidebar nav items
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
import { useState } from 'react';
|
||||
import { useParams, Link } from 'react-router';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { Plus, Network, Wifi, WifiOff } from 'lucide-react';
|
||||
import { Plus, Network, Wifi, WifiOff, Copy, Check } from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
import { api } from '@/lib/api';
|
||||
import { formatBytes } from '@/lib/utils';
|
||||
import { Button } from '@/components/ui/button';
|
||||
@@ -16,6 +17,7 @@ import {
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
DialogDescription,
|
||||
} from '@/components/ui/dialog';
|
||||
|
||||
interface NodeItem {
|
||||
@@ -29,6 +31,10 @@ interface NodeItem {
|
||||
isOnline: boolean;
|
||||
}
|
||||
|
||||
interface CreatedNode extends NodeItem {
|
||||
daemonToken: string;
|
||||
}
|
||||
|
||||
interface PaginatedResponse<T> {
|
||||
data: T[];
|
||||
meta: { total: number };
|
||||
@@ -38,6 +44,9 @@ export function NodesPage() {
|
||||
const { orgId } = useParams();
|
||||
const queryClient = useQueryClient();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [tokenDialog, setTokenDialog] = useState(false);
|
||||
const [createdToken, setCreatedToken] = useState('');
|
||||
const [copied, setCopied] = useState(false);
|
||||
const [name, setName] = useState('');
|
||||
const [fqdn, setFqdn] = useState('');
|
||||
const [daemonPort, setDaemonPort] = useState(8443);
|
||||
@@ -52,17 +61,28 @@ export function NodesPage() {
|
||||
|
||||
const createMutation = useMutation({
|
||||
mutationFn: (body: Record<string, unknown>) =>
|
||||
api.post(`/organizations/${orgId}/nodes`, body),
|
||||
onSuccess: () => {
|
||||
api.post<CreatedNode>(`/organizations/${orgId}/nodes`, body),
|
||||
onSuccess: (node) => {
|
||||
queryClient.invalidateQueries({ queryKey: ['nodes', orgId] });
|
||||
setOpen(false);
|
||||
setName('');
|
||||
setFqdn('');
|
||||
// Show token dialog
|
||||
setCreatedToken(node.daemonToken);
|
||||
setTokenDialog(true);
|
||||
setCopied(false);
|
||||
},
|
||||
});
|
||||
|
||||
const nodes = data?.data ?? [];
|
||||
|
||||
const handleCopyToken = async () => {
|
||||
await navigator.clipboard.writeText(createdToken);
|
||||
setCopied(true);
|
||||
toast.success('Token copied to clipboard');
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
@@ -151,6 +171,45 @@ export function NodesPage() {
|
||||
</Dialog>
|
||||
</div>
|
||||
|
||||
{/* Token display dialog */}
|
||||
<Dialog open={tokenDialog} onOpenChange={setTokenDialog}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Node Created Successfully</DialogTitle>
|
||||
<DialogDescription>
|
||||
Save this daemon token now. It will not be shown again.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="space-y-3">
|
||||
<Label>Daemon Token</Label>
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
readOnly
|
||||
value={createdToken}
|
||||
className="font-mono text-xs"
|
||||
/>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={handleCopyToken}
|
||||
>
|
||||
{copied ? (
|
||||
<Check className="h-4 w-4 text-green-500" />
|
||||
) : (
|
||||
<Copy className="h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Use this token in your daemon configuration file (config.yml) to authenticate with the panel.
|
||||
</p>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button onClick={() => setTokenDialog(false)}>Done</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
{nodes.map((node) => (
|
||||
<Link key={node.id} to={`/org/${orgId}/nodes/${node.id}`}>
|
||||
|
||||
Reference in New Issue
Block a user