Compare commits

...

2 Commits

+67 -4
View File
@@ -1,6 +1,7 @@
import Box from '@mui/material/Box'
import Button from '@mui/material/Button'
import Chip from '@mui/material/Chip'
import Link from '@mui/material/Link'
import List from '@mui/material/List'
import ListItem from '@mui/material/ListItem'
import TextField from '@mui/material/TextField'
@@ -48,6 +49,7 @@ export default function AdminSSHServersPage() {
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const [search, setSearch] = useState('')
const [groupSearch, setGroupSearch] = useState('')
const [dialogOpen, setDialogOpen] = useState(false)
const [editingID, setEditingID] = useState<string | null>(null)
const [form, setForm] = useState<SSHServerFormState>(emptyForm())
@@ -119,6 +121,39 @@ export default function AdminSSHServersPage() {
)
}, [items, search])
const filteredGroups = useMemo(() => {
const needle: string = groupSearch.trim().toLowerCase()
if (!needle) {
return groups
}
return groups.filter((item: SSHServerGroup) =>
[
item.name,
item.id,
item.description,
(item.server_ids || []).join(' ')
]
.join(' ')
.toLowerCase()
.includes(needle)
)
}, [groups, groupSearch])
const groupsByServerID = useMemo(() => {
const next: Record<string, SSHServerGroup[]> = {}
groups.forEach((group: SSHServerGroup) => {
(group.server_ids || []).forEach((serverID: string) => {
if (!next[serverID]) {
next[serverID] = []
}
next[serverID].push(group)
})
})
return next
}, [groups])
const openCreate = () => {
setEditingID(null)
setForm(emptyForm())
@@ -444,7 +479,10 @@ export default function AdminSSHServersPage() {
</Typography>
) : null}
<List>
{filteredItems.map((item) => (
{filteredItems.map((item) => {
const serverGroups: SSHServerGroup[] = groupsByServerID[item.id] || []
return (
<ListItem key={item.id} divider sx={{ alignItems: 'flex-start' }}>
<CompactListItemText
primary={
@@ -456,6 +494,19 @@ export default function AdminSSHServersPage() {
secondary={
<Typography variant="caption" color="text.secondary">
{item.host}:{item.port}· Tags: {(item.tags || []).join(', ') || '-'}· Updated: {fmt(item.updated_at)}
<br />
Groups: {serverGroups.length ? serverGroups.map((group: SSHServerGroup, index: number) => (
<Box key={group.id} component="span">
{index > 0 ? ',' : ''}
<Link
underline="hover"
onClick={() => setViewGroupItem(group)}
sx={{ cursor: 'default' }}
>
{group.name}
</Link>
</Box>
)) : '-'}
{item.description? (<><br />{item.description}</>): null}
</Typography>
}
@@ -473,7 +524,8 @@ export default function AdminSSHServersPage() {
<ListRowActionButton onClick={() => void openHostKeys(item)}>Host Keys</ListRowActionButton>
</ListRowActions>
</ListItem>
))}
)
})}
</List>
</Box>
</SectionCard>
@@ -481,7 +533,18 @@ export default function AdminSSHServersPage() {
<SectionCard
collapsible
collapseStorageKey="admin-ssh-servers:ssh-server-groups"
title={`SSH Server Groups (${groups.length})`}
title={
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, minWidth: 0, flexWrap: 'wrap' }}>
<Typography variant="h6">SSH Server Groups ({filteredGroups.length})</Typography>
<TextField
size="small"
label="Search"
value={groupSearch}
onChange={(event) => setGroupSearch(event.target.value)}
sx={{ minWidth: 240, maxWidth: 320 }}
/>
</Box>
}
actions={
<Button variant="outlined" onClick={openGroupCreate}>
New Group
@@ -489,7 +552,7 @@ export default function AdminSSHServersPage() {
}
>
<List>
{groups.map((item) => (
{filteredGroups.map((item) => (
<ListItem key={item.id} divider sx={{ alignItems: 'flex-start' }}>
<CompactListItemText
primary={