Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4e7d3fc0ec | |||
| 2d14e02211 |
@@ -1,6 +1,7 @@
|
|||||||
import Box from '@mui/material/Box'
|
import Box from '@mui/material/Box'
|
||||||
import Button from '@mui/material/Button'
|
import Button from '@mui/material/Button'
|
||||||
import Chip from '@mui/material/Chip'
|
import Chip from '@mui/material/Chip'
|
||||||
|
import Link from '@mui/material/Link'
|
||||||
import List from '@mui/material/List'
|
import List from '@mui/material/List'
|
||||||
import ListItem from '@mui/material/ListItem'
|
import ListItem from '@mui/material/ListItem'
|
||||||
import TextField from '@mui/material/TextField'
|
import TextField from '@mui/material/TextField'
|
||||||
@@ -48,6 +49,7 @@ export default function AdminSSHServersPage() {
|
|||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const [error, setError] = useState<string | null>(null)
|
const [error, setError] = useState<string | null>(null)
|
||||||
const [search, setSearch] = useState('')
|
const [search, setSearch] = useState('')
|
||||||
|
const [groupSearch, setGroupSearch] = useState('')
|
||||||
const [dialogOpen, setDialogOpen] = useState(false)
|
const [dialogOpen, setDialogOpen] = useState(false)
|
||||||
const [editingID, setEditingID] = useState<string | null>(null)
|
const [editingID, setEditingID] = useState<string | null>(null)
|
||||||
const [form, setForm] = useState<SSHServerFormState>(emptyForm())
|
const [form, setForm] = useState<SSHServerFormState>(emptyForm())
|
||||||
@@ -119,6 +121,39 @@ export default function AdminSSHServersPage() {
|
|||||||
)
|
)
|
||||||
}, [items, search])
|
}, [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 = () => {
|
const openCreate = () => {
|
||||||
setEditingID(null)
|
setEditingID(null)
|
||||||
setForm(emptyForm())
|
setForm(emptyForm())
|
||||||
@@ -444,7 +479,10 @@ export default function AdminSSHServersPage() {
|
|||||||
</Typography>
|
</Typography>
|
||||||
) : null}
|
) : null}
|
||||||
<List>
|
<List>
|
||||||
{filteredItems.map((item) => (
|
{filteredItems.map((item) => {
|
||||||
|
const serverGroups: SSHServerGroup[] = groupsByServerID[item.id] || []
|
||||||
|
|
||||||
|
return (
|
||||||
<ListItem key={item.id} divider sx={{ alignItems: 'flex-start' }}>
|
<ListItem key={item.id} divider sx={{ alignItems: 'flex-start' }}>
|
||||||
<CompactListItemText
|
<CompactListItemText
|
||||||
primary={
|
primary={
|
||||||
@@ -456,6 +494,19 @@ export default function AdminSSHServersPage() {
|
|||||||
secondary={
|
secondary={
|
||||||
<Typography variant="caption" color="text.secondary">
|
<Typography variant="caption" color="text.secondary">
|
||||||
{item.host}:{item.port}· Tags: {(item.tags || []).join(', ') || '-'}· Updated: {fmt(item.updated_at)}
|
{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}
|
{item.description? (<><br />{item.description}</>): null}
|
||||||
</Typography>
|
</Typography>
|
||||||
}
|
}
|
||||||
@@ -473,7 +524,8 @@ export default function AdminSSHServersPage() {
|
|||||||
<ListRowActionButton onClick={() => void openHostKeys(item)}>Host Keys</ListRowActionButton>
|
<ListRowActionButton onClick={() => void openHostKeys(item)}>Host Keys</ListRowActionButton>
|
||||||
</ListRowActions>
|
</ListRowActions>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
))}
|
)
|
||||||
|
})}
|
||||||
</List>
|
</List>
|
||||||
</Box>
|
</Box>
|
||||||
</SectionCard>
|
</SectionCard>
|
||||||
@@ -481,7 +533,18 @@ export default function AdminSSHServersPage() {
|
|||||||
<SectionCard
|
<SectionCard
|
||||||
collapsible
|
collapsible
|
||||||
collapseStorageKey="admin-ssh-servers:ssh-server-groups"
|
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={
|
actions={
|
||||||
<Button variant="outlined" onClick={openGroupCreate}>
|
<Button variant="outlined" onClick={openGroupCreate}>
|
||||||
New Group
|
New Group
|
||||||
@@ -489,7 +552,7 @@ export default function AdminSSHServersPage() {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<List>
|
<List>
|
||||||
{groups.map((item) => (
|
{filteredGroups.map((item) => (
|
||||||
<ListItem key={item.id} divider sx={{ alignItems: 'flex-start' }}>
|
<ListItem key={item.id} divider sx={{ alignItems: 'flex-start' }}>
|
||||||
<CompactListItemText
|
<CompactListItemText
|
||||||
primary={
|
primary={
|
||||||
|
|||||||
Reference in New Issue
Block a user