}>
Dashboard
-
}>
+
}>
Secrets
+ {renderCount(secretsQuery.data?.length)}
-
}>
+
}>
Keys
+ {renderCount(keysQuery.data?.length)}
}>
Certs
+ {renderCount(certsQuery.data?.length)}
}>
Audit Log
diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx
index 24f4cce..4fe79b6 100644
--- a/src/components/layout/Sidebar.tsx
+++ b/src/components/layout/Sidebar.tsx
@@ -1,6 +1,7 @@
import {
Badge,
Button,
+ Input,
makeStyles,
mergeClasses,
Text,
@@ -8,50 +9,18 @@ import {
tokens,
} from '@fluentui/react-components';
import {
- Certificate24Regular,
- ClipboardTextLtr24Regular,
- Delete24Regular,
- Key24Regular,
- LockClosed24Regular,
+ Search24Regular,
ShieldLock24Regular,
Star24Filled,
Star24Regular,
- TextBulletListSquare24Regular,
} from '@fluentui/react-icons';
-import { useQuery } from '@tanstack/react-query';
-import { listCertificates, listKeys, listSecrets } from '../../services/tauri';
+import { useMemo, useState } from 'react';
import { useAppStore } from '../../stores/appStore';
-import type { ItemTab } from '../../types';
-
-interface NavItem {
- id: ItemTab;
- label: string;
- icon: React.ReactElement;
- countKey?: string;
-}
-
-const navIconStyle = { fontSize: 16 } as const;
-
-const VAULT_NAV: NavItem[] = [
- {
- id: 'dashboard',
- label: 'Dashboard',
- icon:
,
- },
- { id: 'secrets', label: 'Secrets', icon:
},
- { id: 'keys', label: 'Keys', icon:
},
- {
- id: 'certificates',
- label: 'Certificates',
- icon:
,
- },
- { id: 'logs', label: 'Audit Log', icon:
},
-];
const useStyles = makeStyles({
root: {
- width: '220px',
- minWidth: '220px',
+ width: '240px',
+ minWidth: '240px',
height: '100%',
display: 'flex',
flexDirection: 'column',
@@ -70,305 +39,258 @@ const useStyles = makeStyles({
width: '36px',
height: '36px',
},
- section: {
+ header: {
+ display: 'flex',
+ alignItems: 'center',
+ gap: '6px',
padding: '8px 10px 4px',
},
- sectionRecent: {
- padding: '4px 10px',
+ headerTitle: {
+ flex: 1,
+ },
+ filterWrap: {
+ padding: '0 10px 6px',
+ },
+ filterInput: {
+ width: '100%',
+ },
+ scroll: {
+ flex: 1,
+ overflowY: 'auto',
+ minHeight: 0,
+ padding: '0 10px 10px',
+ },
+ section: {
+ marginTop: '6px',
},
sectionLabel: {
- marginBottom: '4px',
- padding: '0 4px',
+ padding: '2px 4px',
+ marginBottom: '2px',
},
- sectionHeader: {
+ vaultRow: {
display: 'flex',
alignItems: 'center',
- justifyContent: 'space-between',
- padding: '0 4px',
- },
- clearBtn: {
- width: '20px',
- height: '20px',
- minWidth: '20px',
+ gap: '2px',
+ marginBottom: '2px',
},
- vaultItem: {
- padding: '4px 8px',
- borderRadius: '4px',
- cursor: 'pointer',
+ vaultSelect: {
+ flex: 1,
+ minWidth: 0,
display: 'flex',
alignItems: 'center',
gap: '6px',
- marginBottom: '2px',
+ padding: '5px 8px',
+ borderRadius: '4px',
+ cursor: 'pointer',
+ width: '100%',
+ backgroundColor: 'transparent',
+ color: 'inherit',
+ fontFamily: 'inherit',
+ fontSize: 'inherit',
+ textAlign: 'left',
},
- vaultItemSelected: {
+ vaultSelectSelected: {
backgroundColor: tokens.colorBrandBackground2,
},
- starIcon: {
- fontSize: '12px',
- color: tokens.colorPaletteYellowForeground1,
+ vaultIcon: {
+ fontSize: '14px',
+ opacity: 0.6,
flexShrink: 0,
},
- recentIcon: {
- fontSize: '12px',
- opacity: 0.5,
+ starIcon: {
+ fontSize: '14px',
+ color: tokens.colorPaletteYellowForeground1,
flexShrink: 0,
},
vaultName: {
flex: 1,
- },
- divider: {
- borderTop: `1px solid ${tokens.colorNeutralStroke2}`,
- margin: '4px 10px',
- },
- navSection: {
- padding: '4px 10px',
- },
- navHeader: {
- display: 'flex',
- alignItems: 'center',
- gap: '6px',
- padding: '0 4px',
- marginBottom: '4px',
- },
- navTitle: {
- flex: 1,
+ minWidth: 0,
},
pinBtn: {
width: '24px',
height: '24px',
minWidth: '24px',
- },
- navItem: {
- padding: '6px 8px',
- borderRadius: '4px',
- cursor: 'pointer',
- display: 'flex',
- alignItems: 'center',
- gap: '8px',
- marginBottom: '2px',
- },
- navItemActive: {
- backgroundColor: tokens.colorBrandBackground2,
- fontWeight: 600,
- },
- navIcon: {
- opacity: 0.6,
- },
- navIconActive: {
- opacity: 1,
- },
- navLabel: {
- flex: 1,
+ flexShrink: 0,
},
emptyState: {
- padding: '20px',
+ padding: '24px 16px',
textAlign: 'center' as const,
},
emptyIcon: {
- fontSize: '32px',
- opacity: 0.3,
+ fontSize: '30px',
+ opacity: 0.4,
},
emptyText: {
- color: tokens.colorNeutralForeground3,
+ color: tokens.colorNeutralForeground2,
marginTop: '8px',
+ lineHeight: 1.5,
},
});
export function Sidebar() {
const {
selectedVaultUri,
- selectedVaultName,
- activeTab,
- setActiveTab,
+ keyvaults,
pinnedVaults,
- recentVaults,
+ selectedSubscriptionId,
+ selectedTenantId,
selectVault,
- unpinVault,
pinVault,
- clearRecentVaults,
+ unpinVault,
sidebarCollapsed,
- selectedTenantId,
- selectedSubscriptionId,
} = useAppStore();
const classes = useStyles();
+ const [filter, setFilter] = useState('');
- const secretsQuery = useQuery({
- queryKey: ['secrets', selectedVaultUri],
- queryFn: () => listSecrets(selectedVaultUri!),
- enabled: !!selectedVaultUri,
- });
- const keysQuery = useQuery({
- queryKey: ['keys', selectedVaultUri],
- queryFn: () => listKeys(selectedVaultUri!),
- enabled: !!selectedVaultUri,
- });
- const certsQuery = useQuery({
- queryKey: ['certificates', selectedVaultUri],
- queryFn: () => listCertificates(selectedVaultUri!),
- enabled: !!selectedVaultUri,
- });
+ const isPinned = (uri: string) => pinnedVaults.some((v) => v.uri === uri);
- const counts: Record
= {
- secrets: secretsQuery.data?.length,
- keys: keysQuery.data?.length,
- certificates: certsQuery.data?.length,
+ const togglePin = (name: string, uri: string) => {
+ if (isPinned(uri)) {
+ unpinVault(uri);
+ } else if (selectedTenantId && selectedSubscriptionId) {
+ pinVault({ name, uri, tenantId: selectedTenantId, subscriptionId: selectedSubscriptionId });
+ }
};
- const isPinned = pinnedVaults.some((v) => v.uri === selectedVaultUri);
+ const q = filter.trim().toLowerCase();
+ const subVaults = useMemo(
+ () => keyvaults.filter((v) => !q || v.name.toLowerCase().includes(q)),
+ [keyvaults, q],
+ );
+ const pinnedFiltered = useMemo(
+ () => pinnedVaults.filter((v) => !q || v.name.toLowerCase().includes(q)),
+ [pinnedVaults, q],
+ );
if (sidebarCollapsed) {
return (
-
- {selectedVaultName &&
- VAULT_NAV.map((item) => (
-
-
- ))}
-
+
);
}
- return (
-
- {pinnedVaults.length > 0 && (
-
-
- Pinned
+ const renderVaultRow = (name: string, uri: string, key: string) => {
+ const pinned = isPinned(uri);
+ const selected = uri === selectedVaultUri;
+ return (
+
+
+
+
+ ) : (
+
+ )
+ }
+ onClick={() => togglePin(name, uri)}
+ />
+
+
+ );
+ };
+
+ const hasAnything = pinnedVaults.length > 0 || keyvaults.length > 0;
+
+ return (
+