From 63db8975a52d36cc7bd421d7c776c4c3a713c2ca Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Thu, 7 May 2026 21:10:22 +0000 Subject: [PATCH] Nav bar and page title cleanup --- src/App.tsx | 2 - src/components/navigation/AppSidebar.tsx | 5 +- src/pages/org/AccessPage.tsx | 2 +- src/pages/org/ApiKeysPage.tsx | 428 ----------------------- src/pages/org/DevicesPage.tsx | 2 +- src/pages/org/NetworksPage.tsx | 2 +- 6 files changed, 5 insertions(+), 436 deletions(-) delete mode 100644 src/pages/org/ApiKeysPage.tsx diff --git a/src/App.tsx b/src/App.tsx index 79319dd..ba357e8 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -51,7 +51,6 @@ import OIDCClientsPage from "@/pages/org/OIDCClientsPage"; import CAsPage from "@/pages/org/CAsPage"; import DepartmentsPage from "@/pages/org/DepartmentsPage"; import PrincipalsPage from "@/pages/org/PrincipalsPage"; -import ApiKeysPage from "@/pages/org/ApiKeysPage"; import MyMembershipsPage from "@/pages/org/MyMembershipsPage"; import NetworksPage from "@/pages/org/NetworksPage"; import NetworkManagementPage from "@/pages/org/NetworkManagementPage"; @@ -203,7 +202,6 @@ function AppRoutes() { } /> } /> } /> - } /> } /> } /> } /> diff --git a/src/components/navigation/AppSidebar.tsx b/src/components/navigation/AppSidebar.tsx index cd88f9a..da09be8 100644 --- a/src/components/navigation/AppSidebar.tsx +++ b/src/components/navigation/AppSidebar.tsx @@ -46,13 +46,14 @@ const userNavItems = [ { title: "Linked Accounts", url: "/linked-accounts", icon: Link2 }, { title: "Activity", url: "/activity", icon: Activity }, { title: "CLI Guide", url: "/cli-guide", icon: BookOpen }, + { title: "ZeroTier Devices", url: "/org/zerotier/devices", icon: Monitor }, + ]; // Visible to ALL org members const orgMemberNavItems = [ { title: "Overview", url: "/org", icon: Building2 }, { title: "My Memberships", url: "/org/my-memberships", icon: Layers }, - { title: "ZeroTier Devices", url: "/org/zerotier/devices", icon: Monitor }, ]; // Visible to org admins/owners only (management) @@ -61,12 +62,10 @@ const orgAdminNavItems = [ { title: "Members", url: "/org/members", icon: Users }, { title: "Departments", url: "/org/departments", icon: Layers }, { title: "Principals", url: "/org/principals", icon: GitBranch }, - { title: "API Keys", url: "/org/api-keys", icon: Key }, { title: "Policies", url: "/org/policies", icon: Settings }, { title: "ZeroTier Networks", url: "/org/zerotier/networks", icon: Network }, { title: "ZeroTier Access", url: "/org/zerotier/access", icon: ShieldAlert }, { title: "ZeroTier Config", url: "/org/zerotier/config", icon: Settings }, - { title: "ZeroTier Devices", url: "/org/zerotier/devices", icon: Monitor }, ]; const adminNavItems = [ diff --git a/src/pages/org/AccessPage.tsx b/src/pages/org/AccessPage.tsx index 269b2c0..acb294b 100644 --- a/src/pages/org/AccessPage.tsx +++ b/src/pages/org/AccessPage.tsx @@ -359,7 +359,7 @@ export default function AccessPage() { return (
-

Access Control

+

ZeroTier Access

Manage network access requests, approvals, and active sessions

diff --git a/src/pages/org/ApiKeysPage.tsx b/src/pages/org/ApiKeysPage.tsx deleted file mode 100644 index 497fd49..0000000 --- a/src/pages/org/ApiKeysPage.tsx +++ /dev/null @@ -1,428 +0,0 @@ -import { useState, useEffect, useRef } from "react"; -import { - Plus, Copy, Trash2, Loader2, AlertCircle, CheckCircle, MoreHorizontal, Edit2, Check -} from "lucide-react"; -import { Button } from "@/components/ui/button"; -import { Card, CardContent } from "@/components/ui/card"; -import { Badge } from "@/components/ui/badge"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuSeparator, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; -import { - Dialog, - DialogContent, - DialogDescription, - DialogHeader, - DialogTitle, -} from "@/components/ui/dialog"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { Textarea } from "@/components/ui/textarea"; -import { api, OrganizationApiKey } from "@/lib/api"; -import { useToast } from "@/hooks/use-toast"; -import { useOrg } from "@/contexts/OrgContext"; -import { formatDate } from "@/lib/date"; -import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; - -interface NewApiKeyState { - key: string; - name: string; - description?: string; - createdAt: string; -} - -interface EditingKey { - id: string; - name: string; - description: string | null; -} - -function useCopyButton() { - const [copied, setCopied] = useState(false); - const copy = (text: string) => { - navigator.clipboard.writeText(text).then(() => { - setCopied(true); - setTimeout(() => setCopied(false), 2000); - }); - }; - return { copied, copy }; -} - -export default function ApiKeysPage() { - const { toast } = useToast(); - const { selectedOrgId: orgId } = useOrg(); - const queryClient = useQueryClient(); - const { copy, copied } = useCopyButton(); - - const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false); - const [isEditDialogOpen, setIsEditDialogOpen] = useState(false); - const [newSecret, setNewSecret] = useState(null); - const [editingKey, setEditingKey] = useState(null); - const [showKey, setShowKey] = useState(false); - const [isCreating, setIsCreating] = useState(false); - - const nameRef = useRef(null); - const descriptionRef = useRef(null); - const editNameRef = useRef(null); - const editDescriptionRef = useRef(null); - - // Fetch API keys - const { data: apiKeysData, isLoading } = useQuery({ - queryKey: ['api-keys', orgId], - queryFn: () => orgId ? api.organizations.getApiKeys(orgId) : null, - enabled: !!orgId, - }); - - // Create API key mutation - const { mutate: createKey, isPending: isCreatingKey } = useMutation({ - mutationFn: () => { - if (!orgId) throw new Error('Organization ID not set'); - const name = nameRef.current?.value; - const description = descriptionRef.current?.value; - if (!name) throw new Error('Name is required'); - return api.organizations.createApiKey(orgId, name, description); - }, - onSuccess: (data) => { - const apiKey = data.api_key; - setNewSecret({ - key: apiKey.key || '', - name: apiKey.name, - description: apiKey.description || undefined, - createdAt: apiKey.created_at, - }); - setIsCreateDialogOpen(false); - if (nameRef.current) nameRef.current.value = ''; - if (descriptionRef.current) descriptionRef.current.value = ''; - queryClient.invalidateQueries({ queryKey: ['api-keys', orgId] }); - toast({ - title: 'API Key Created', - description: 'Store the key value securely - you won\'t be able to see it again.', - }); - }, - onError: () => { - toast({ - title: 'Failed to create API key', - description: 'Please try again.', - variant: 'destructive', - }); - }, - }); - - // Update API key mutation - const { mutate: updateKey, isPending: isUpdatingKey } = useMutation({ - mutationFn: () => { - if (!orgId || !editingKey) throw new Error('Required data missing'); - return api.organizations.updateApiKey(orgId, editingKey.id, { - name: editNameRef.current?.value, - description: editDescriptionRef.current?.value, - }); - }, - onSuccess: () => { - setIsEditDialogOpen(false); - queryClient.invalidateQueries({ queryKey: ['api-keys', orgId] }); - toast({ - title: 'API Key Updated', - description: 'Changes saved successfully.', - }); - }, - onError: () => { - toast({ - title: 'Failed to update API key', - description: 'Please try again.', - variant: 'destructive', - }); - }, - }); - - // Delete API key mutation - const { mutate: deleteKey, isPending: isDeletingKey } = useMutation({ - mutationFn: (keyId: string) => { - if (!orgId) throw new Error('Organization ID not set'); - return api.organizations.deleteApiKey(orgId, keyId); - }, - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ['api-keys', orgId] }); - toast({ - title: 'API Key Deleted', - description: 'The API key has been permanently removed.', - }); - }, - onError: () => { - toast({ - title: 'Failed to delete API key', - description: 'Please try again.', - variant: 'destructive', - }); - }, - }); - - const handleCreateKey = () => { - setIsCreating(true); - createKey(); - setIsCreating(false); - }; - - const handleEditKey = (key: OrganizationApiKey) => { - setEditingKey({ - id: key.id, - name: key.name, - description: key.description, - }); - setIsEditDialogOpen(true); - }; - - const handleUpdateKey = () => { - updateKey(); - }; - - const handleDeleteKey = (keyId: string) => { - if (confirm('Are you sure you want to delete this API key? This action cannot be undone.')) { - deleteKey(keyId); - } - }; - - const apiKeys = apiKeysData?.api_keys || []; - const activeKeys = apiKeys.filter(k => !k.is_revoked); - const revokedKeys = apiKeys.filter(k => k.is_revoked); - - if (isLoading) { - return ( -
-
- -
-
- ); - } - - return ( -
-
-
-

API Keys

-

Manage API keys for programmatic access to your organization.

-
- -
- - {/* New key reveal banner */} - {newSecret && ( -
-
- - API key created — copy it now, you won't see it again. -
-
- - {newSecret.key} - - -
- -
- )} - - {/* Key list */} - - - {isLoading ? ( -
- - Loading... -
- ) : apiKeys.length === 0 ? ( -
- -

No API keys yet

-

Create one to enable external integrations.

- -
- ) : ( -
- {activeKeys.map((key) => ( -
-
-
- {key.name} - {key.last_used_at && ( - - Last used {formatDate(key.last_used_at)} - - )} -
- {key.description && ( -

{key.description}

- )} -

Created {formatDate(key.created_at)}

-
- - - - - - handleEditKey(key)} className="cursor-pointer"> - Edit - - - handleDeleteKey(key.id)} - className="text-destructive cursor-pointer" - disabled={isDeletingKey} - > - Delete - - - -
- ))} - - {revokedKeys.length > 0 && ( - <> -
- Revoked -
- {revokedKeys.map((key) => ( -
-
-

{key.name}

-

- Revoked {formatDate(key.revoked_at || '')} - {key.revoke_reason && ` — ${key.revoke_reason}`} -

-
-
- ))} - - )} -
- )} -
-
- - {/* Create Dialog */} - - - - Create API Key - - Create a new API key for external integrations. The key will be displayed only once. - - -
-
- - -
-
- -