Private
Public Access
1
0

Revert "ci: adapt CI to ubuntu-22.04 runner with proven linux_patch_api patterns"

This reverts commit f8bac85903.
This commit is contained in:
2026-04-27 03:02:53 +00:00
parent f8bac85903
commit 8a27b136b7
5 changed files with 229 additions and 425 deletions

View File

@ -1,8 +1,9 @@
import { useEffect, useState } from 'react'
import {
Box, Button, Chip, CircularProgress, Container, Dialog, DialogActions,
DialogContent, DialogTitle, IconButton, MenuItem, Paper, Select, Table,
TableBody, TableCell, TableContainer, TableHead, TableRow, TextField, Toolbar, Tooltip, Typography,
DialogContent, DialogTitle, IconButton, MenuItem, Paper, Select,
Table, TableBody, TableCell, TableContainer, TableHead, TableRow,
TextField, Toolbar, Tooltip, Typography,
} from '@mui/material'
import { Add as AddIcon, Lock as LockIcon } from '@mui/icons-material'
import { apiClient } from '../api/client'
@ -16,16 +17,26 @@ export default function UsersPage() {
const load = async () => {
setLoading(true)
try { const r = await apiClient.get('/users'); setUsers(r.data) }
try {
const r = await apiClient.get('/users')
setUsers(r.data)
} catch { /* interceptor handles */ }
finally { setLoading(false) }
}
useEffect(() => { load() }, [])
const handleCreate = async () => {
await apiClient.post('/users', form)
setOpen(false); setForm({ username: '', email: '', role: 'operator', password: '' })
load()
try {
await apiClient.post('/users', form)
setOpen(false)
setForm({ username: '', email: '', role: 'operator', password: '' })
load()
} catch { /* interceptor handles */ }
}
const handleRevoke = async (id: string) => {
await apiClient.post(`/users/${id}/revoke`)
}
return (
@ -34,24 +45,45 @@ export default function UsersPage() {
<Typography variant="h5" fontWeight={700} sx={{ flexGrow: 1 }}>Users</Typography>
<Button variant="contained" startIcon={<AddIcon />} onClick={() => setOpen(true)}>Add User</Button>
</Toolbar>
{loading ? <Box display="flex" justifyContent="center" mt={4}><CircularProgress /></Box> : (
{loading ? (
<Box display="flex" justifyContent="center" mt={4}><CircularProgress /></Box>
) : (
<TableContainer component={Paper}>
<Table size="small">
<TableHead><TableRow>
<TableCell>Username</TableCell><TableCell>Email</TableCell>
<TableCell>Role</TableCell><TableCell>MFA</TableCell>
<TableCell>Status</TableCell><TableCell>Actions</TableCell>
</TableRow></TableHead>
<TableHead>
<TableRow>
<TableCell>Username</TableCell>
<TableCell>Email</TableCell>
<TableCell>Role</TableCell>
<TableCell>MFA</TableCell>
<TableCell>Status</TableCell>
<TableCell>Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
{users.map(u => (
<TableRow key={u.id} hover>
<TableCell>{u.username}</TableCell>
<TableCell>{u.email}</TableCell>
<TableCell><Chip size="small" label={u.role} color={u.role === 'admin' ? 'primary' : 'default'} /></TableCell>
<TableCell><Chip size="small" label={u.mfa_enabled ? 'On' : 'Off'} color={u.mfa_enabled ? 'success' : 'warning'} /></TableCell>
<TableCell><Chip size="small" label={u.is_active ? 'Active' : 'Disabled'} color={u.is_active ? 'success' : 'error'} /></TableCell>
<TableCell>
<Tooltip title="Revoke Sessions"><IconButton size="small" color="warning" onClick={() => apiClient.post(`/users/${u.id}/revoke`)}><LockIcon fontSize="small" /></IconButton></Tooltip>
<Chip size="small" label={u.role}
color={u.role === 'admin' ? 'primary' : 'default'} />
</TableCell>
<TableCell>
<Chip size="small" label={u.mfa_enabled ? 'On' : 'Off'}
color={u.mfa_enabled ? 'success' : 'warning'} />
</TableCell>
<TableCell>
<Chip size="small" label={u.is_active ? 'Active' : 'Disabled'}
color={u.is_active ? 'success' : 'error'} />
</TableCell>
<TableCell>
<Tooltip title="Revoke All Sessions">
<IconButton size="small" color="warning" onClick={() => handleRevoke(u.id)}>
<LockIcon fontSize="small" />
</IconButton>
</Tooltip>
</TableCell>
</TableRow>
))}
@ -59,7 +91,37 @@ export default function UsersPage() {
</Table>
</TableContainer>
)}
<Dialog open={open} onClose={() => setOpen(false)} maxWidth="xs" fullWidth>
<DialogTitle>Add User</DialogTitle>
<DialogContent>
<TextField fullWidth label="Username" value={form.username} onChange={e => setForm({...form, username: e.target.value})}
<TextField fullWidth label="Username"
value={form.username}
onChange={e => setForm({ ...form, username: e.target.value })}
margin="normal" required />
<TextField fullWidth label="Email" type="email"
value={form.email}
onChange={e => setForm({ ...form, email: e.target.value })}
margin="normal" required />
<TextField fullWidth label="Password" type="password"
value={form.password}
onChange={e => setForm({ ...form, password: e.target.value })}
margin="normal" required />
<Select fullWidth value={form.role}
onChange={e => setForm({ ...form, role: e.target.value })}
sx={{ mt: 1 }}>
<MenuItem value="operator">Operator</MenuItem>
<MenuItem value="admin">Admin</MenuItem>
</Select>
</DialogContent>
<DialogActions>
<Button onClick={() => setOpen(false)}>Cancel</Button>
<Button variant="contained" onClick={handleCreate}
disabled={!form.username || !form.email || !form.password}>
Create
</Button>
</DialogActions>
</Dialog>
</Container>
)
}