Compare commits

...

2 Commits

37 changed files with 132 additions and 126 deletions
+22
View File
@@ -0,0 +1,22 @@
import Alert, { AlertColor } from '@mui/material/Alert'
import { SxProps, Theme } from '@mui/material/styles'
import { ReactNode } from 'react'
type PageAlertProps = {
children?: ReactNode
message?: ReactNode | null
onClose?: () => void
severity?: AlertColor
sx?: SxProps<Theme>
}
export default function PageAlert(props: PageAlertProps) {
const content: ReactNode = props.children || props.message
if (!content) return null
return (
<Alert severity={props.severity || 'error'} onClose={props.onClose} sx={props.sx}>
{content}
</Alert>
)
}
@@ -1,4 +1,3 @@
import Alert from '@mui/material/Alert'
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 Dialog from '@mui/material/Dialog' import Dialog from '@mui/material/Dialog'
@@ -10,6 +9,7 @@ import TextField from '@mui/material/TextField'
import Typography from '@mui/material/Typography' import Typography from '@mui/material/Typography'
import { ChangeEvent as ReactChangeEvent, useEffect, useState } from 'react' import { ChangeEvent as ReactChangeEvent, useEffect, useState } from 'react'
import { api, BinaryDownload } from '../api' import { api, BinaryDownload } from '../api'
import PageAlert from './PageAlert'
type SSHSessionFileDownloadDialogProps = { type SSHSessionFileDownloadDialogProps = {
open: boolean open: boolean
@@ -87,7 +87,7 @@ export default function SSHSessionFileDownloadDialog(props: SSHSessionFileDownlo
<Dialog open={props.open} onClose={close} maxWidth="sm" fullWidth> <Dialog open={props.open} onClose={close} maxWidth="sm" fullWidth>
<DialogTitle>Download Files</DialogTitle> <DialogTitle>Download Files</DialogTitle>
<DialogContent sx={{ display: 'grid', gap: 2, pt: 1 }}> <DialogContent sx={{ display: 'grid', gap: 2, pt: 1 }}>
{error ? <Alert severity="error">{error}</Alert> : null} <PageAlert message={error} onClose={() => setError(null)} />
<TextField <TextField
label="Remote file paths" label="Remote file paths"
value={pathsText} value={pathsText}
@@ -1,4 +1,3 @@
import Alert from '@mui/material/Alert'
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 Checkbox from '@mui/material/Checkbox' import Checkbox from '@mui/material/Checkbox'
@@ -12,6 +11,7 @@ import TextField from '@mui/material/TextField'
import Typography from '@mui/material/Typography' import Typography from '@mui/material/Typography'
import { ChangeEvent as ReactChangeEvent, useEffect, useState } from 'react' import { ChangeEvent as ReactChangeEvent, useEffect, useState } from 'react'
import { api, SSHSessionFileUploadItem, SSHSessionFileUploadResponse } from '../api' import { api, SSHSessionFileUploadItem, SSHSessionFileUploadResponse } from '../api'
import PageAlert from './PageAlert'
type SSHSessionFileUploadDialogProps = { type SSHSessionFileUploadDialogProps = {
open: boolean open: boolean
@@ -102,7 +102,7 @@ export default function SSHSessionFileUploadDialog(props: SSHSessionFileUploadDi
<Dialog open={props.open} onClose={close} maxWidth="sm" fullWidth> <Dialog open={props.open} onClose={close} maxWidth="sm" fullWidth>
<DialogTitle>Upload Files</DialogTitle> <DialogTitle>Upload Files</DialogTitle>
<DialogContent sx={{ display: 'grid', gap: 2, pt: 1 }}> <DialogContent sx={{ display: 'grid', gap: 2, pt: 1 }}>
{error ? <Alert severity="error">{error}</Alert> : null} <PageAlert message={error} onClose={() => setError(null)} />
<TextField <TextField
label="Target directory" label="Target directory"
value={targetDir} value={targetDir}
@@ -150,11 +150,11 @@ export default function SSHSessionFileUploadDialog(props: SSHSessionFileUploadDi
fullWidth fullWidth
/> />
) : null} ) : null}
{result ? ( <PageAlert
<Alert severity={result.failed > 0 ? 'warning' : 'success'}> severity={result && result.failed > 0 ? 'warning' : 'success'}
Uploaded {result.uploaded}; failed {result.failed}. message={result ? `Uploaded ${result.uploaded}; failed ${result.failed}.` : null}
</Alert> onClose={() => setResult(null)}
) : null} />
{result && result.items.length > 0 ? ( {result && result.items.length > 0 ? (
<Box sx={{ display: 'grid', maxHeight: 140, overflow: 'auto' }}> <Box sx={{ display: 'grid', maxHeight: 140, overflow: 'auto' }}>
{result.items.map((item: SSHSessionFileUploadItem, index: number) => ( {result.items.map((item: SSHSessionFileUploadItem, index: number) => (
+1 -2
View File
@@ -63,8 +63,7 @@ export default function SectionCard(props: SectionCardProps) {
maxWidth: '100%', maxWidth: '100%',
minWidth: 0, minWidth: 0,
borderRadius: 1, borderRadius: 1,
backgroundColor: (theme) => backgroundColor: (theme) => theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.02)' : 'rgba(0,0,0,0.015)'
theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.02)' : 'rgba(0,0,0,0.015)'
}} }}
> >
<Box <Box
+3 -8
View File
@@ -1,12 +1,8 @@
import Paper, { PaperProps } from '@mui/material/Paper' import Paper, { PaperProps } from '@mui/material/Paper'
function mergeSx(base: any, extra: any) { function mergeSx(base: any, extra: any) {
if (!extra) { if (!extra) return base
return base if (Array.isArray(extra)) return [base, ...extra]
}
if (Array.isArray(extra)) {
return [base, ...extra]
}
return [base, extra] return [base, extra]
} }
@@ -19,8 +15,7 @@ export default function TintedPanel(props: PaperProps) {
sx={mergeSx( sx={mergeSx(
{ {
p: 2, p: 2,
backgroundColor: (theme: any) => backgroundColor: (theme: any) => theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.02)' : 'rgba(0,0,0,0.015)'
theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.02)' : 'rgba(0,0,0,0.015)'
}, },
sx sx
)} )}
+4 -3
View File
@@ -1,3 +1,4 @@
import PageAlert from '../components/PageAlert'
import Alert from '@mui/material/Alert' import Alert from '@mui/material/Alert'
import { Box, Button, Paper, TextField, Typography } from '@mui/material' import { Box, Button, Paper, TextField, Typography } from '@mui/material'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
@@ -155,8 +156,8 @@ export default function AccountPage() {
My Account My Account
</Typography> </Typography>
<Paper sx={{ p: 2, maxWidth: 560, backgroundColor: 'transparent' }}> <Paper sx={{ p: 2, maxWidth: 560, backgroundColor: 'transparent' }}>
{error ? <Alert severity="error" sx={{ mb: 1 }}>{error}</Alert> : null} <PageAlert message={error} onClose={() => setError(null)} sx={{ mb: 1 }} />
{saved ? <Alert severity="success" sx={{ mb: 1 }}>Saved.</Alert> : null} <PageAlert severity="success" message={saved ? 'Saved.' : null} onClose={() => setSaved(false)} sx={{ mb: 1 }} />
<Box sx={{ display: 'grid', gap: 1 }}> <Box sx={{ display: 'grid', gap: 1 }}>
<TextField label="Username" value={user?.username || ''} disabled /> <TextField label="Username" value={user?.username || ''} disabled />
<TextField <TextField
@@ -195,7 +196,7 @@ export default function AccountPage() {
<Typography variant="h6" sx={{ mb: 1 }}> <Typography variant="h6" sx={{ mb: 1 }}>
Authenticator App Authenticator App
</Typography> </Typography>
{totpError ? <Alert severity="error" sx={{ mb: 1 }}>{totpError}</Alert> : null} <PageAlert message={totpError} onClose={() => setTOTPError(null)} sx={{ mb: 1 }} />
<Typography variant="body2" color="text.secondary" sx={{ mb: 1 }}> <Typography variant="body2" color="text.secondary" sx={{ mb: 1 }}>
Status: {totpEnabled ? 'Enabled' : 'Disabled'}{totpRequired ? ' · Required' : ''}{totpSetupRequired ? ' · Setup required' : ''}{totpVerifyRequired ? ' · Verification required' : ''} Status: {totpEnabled ? 'Enabled' : 'Disabled'}{totpRequired ? ' · Required' : ''}{totpSetupRequired ? ' · Setup required' : ''}{totpVerifyRequired ? ' · Verification required' : ''}
</Typography> </Typography>
+2 -1
View File
@@ -19,6 +19,7 @@ import SectionCard from '../components/SectionCard'
import { AdminAPIKey, api } from '../api' import { AdminAPIKey, api } from '../api'
import SelectField from '../components/SelectField' import SelectField from '../components/SelectField'
import CompactListItemText from '../components/CompactListItemText' import CompactListItemText from '../components/CompactListItemText'
import PageAlert from '../components/PageAlert'
function formatUnix(value: number) { function formatUnix(value: number) {
if (!value || value <= 0) { if (!value || value <= 0) {
@@ -188,7 +189,7 @@ export default function AdminApiKeysPage() {
</HeaderActionButton> </HeaderActionButton>
} }
> >
{error ? <Alert severity="error" sx={{ mb: 1 }}>{error}</Alert> : null} <PageAlert message={error} onClose={() => setError(null)} sx={{ mb: 1 }} />
{loading ? ( {loading ? (
<Typography variant="body2" color="text.secondary"> <Typography variant="body2" color="text.secondary">
Loading API keys... Loading API keys...
+3 -3
View File
@@ -1,8 +1,8 @@
import Alert from '@mui/material/Alert'
import { Box, Button, Checkbox, FormControlLabel, MenuItem, TextField, Typography } from '@mui/material' import { Box, Button, Checkbox, FormControlLabel, MenuItem, TextField, Typography } from '@mui/material'
import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline' import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline'
import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline' import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline'
import { useEffect, useRef, useState } from 'react' import { useEffect, useRef, useState } from 'react'
import PageAlert from '../components/PageAlert'
import { api, AuthSettings } from '../api' import { api, AuthSettings } from '../api'
import SectionCard from '../components/SectionCard' import SectionCard from '../components/SectionCard'
import SelectField from '../components/SelectField' import SelectField from '../components/SelectField'
@@ -135,8 +135,8 @@ export default function AdminAuthLdapPage() {
Loading... Loading...
</Typography> </Typography>
) : null} ) : null}
{error ? <Alert severity="error">{error}</Alert> : null} <PageAlert message={error} onClose={() => setError(null)} />
{saved ? <Alert severity="success">Saved.</Alert> : null} <PageAlert severity="success" message={saved ? 'Saved.' : null} onClose={() => setSaved(false)} />
<Box sx={{ display: 'grid', gap: 1, width: '100%', minWidth: 0 }}> <Box sx={{ display: 'grid', gap: 1, width: '100%', minWidth: 0 }}>
<SectionCard <SectionCard
title="General" title="General"
@@ -21,6 +21,7 @@ import SectionCard from '../components/SectionCard'
import { api, PKICA, PKICADetail, PKIClientProfile, PKIClientProfileUpsertPayload, User, UserGroup } from '../api' import { api, PKICA, PKICADetail, PKIClientProfile, PKIClientProfileUpsertPayload, User, UserGroup } from '../api'
import { AppBrand, useBrand } from '../branding' import { AppBrand, useBrand } from '../branding'
import CompactListItemText from '../components/CompactListItemText' import CompactListItemText from '../components/CompactListItemText'
import PageAlert from '../components/PageAlert'
function fmt(value: number): string { function fmt(value: number): string {
if (!value || value <= 0) { if (!value || value <= 0) {
@@ -278,7 +279,7 @@ export default function AdminPKIClientProfilesPage() {
</HeaderActionButton> </HeaderActionButton>
} }
> >
{error ? <Alert severity="error" sx={{ mb: 1 }}>{error}</Alert> : null} <PageAlert message={error} onClose={() => setError(null)} sx={{ mb: 1 }} />
<List dense> <List dense>
{items.map((item) => ( {items.map((item) => (
<ListItem <ListItem
+2 -1
View File
@@ -28,6 +28,7 @@ import SectionCard from '../components/SectionCard'
import { ACMEOrder, ACMEOrderCreatePayload, ACMEProfile, ACMEProfileUpsertPayload, api, PKICA, PKICADetail, PKICert, PKICertDetail, PKICertImportPayload, PKICertIssuePayload, PKIIntermediateCARequest, PKIRootCARequest } from '../api' import { ACMEOrder, ACMEOrderCreatePayload, ACMEProfile, ACMEProfileUpsertPayload, api, PKICA, PKICADetail, PKICert, PKICertDetail, PKICertImportPayload, PKICertIssuePayload, PKIIntermediateCARequest, PKIRootCARequest } from '../api'
import SelectField from '../components/SelectField' import SelectField from '../components/SelectField'
import CompactListItemText from '../components/CompactListItemText' import CompactListItemText from '../components/CompactListItemText'
import PageAlert from '../components/PageAlert'
function fmt(ts: number): string { function fmt(ts: number): string {
if (!ts || ts <= 0) { if (!ts || ts <= 0) {
@@ -681,7 +682,7 @@ export default function AdminPKIPage() {
<Box sx={{ display: 'flex', gap: 1 }}> <Box sx={{ display: 'flex', gap: 1 }}>
</Box> </Box>
</Box> </Box>
{error ? <Alert severity="error" sx={{ mb: 1 }}>{error}</Alert> : null} <PageAlert message={error} onClose={() => setError(null)} sx={{ mb: 1 }} />
<Box sx={{ display: 'grid', gap: 2 }}> <Box sx={{ display: 'grid', gap: 2 }}>
<SectionCard <SectionCard
@@ -23,6 +23,7 @@ import { SSHAccessProfileFormState } from '../components/SSHAccessProfileFormDia
import SSHServerDetailsDialog from '../components/SSHServerDetailsDialog' import SSHServerDetailsDialog from '../components/SSHServerDetailsDialog'
import SSHServerGroupDetailsDialog from '../components/SSHServerGroupDetailsDialog' import SSHServerGroupDetailsDialog from '../components/SSHServerGroupDetailsDialog'
import SSHUserCADetailsDialog from '../components/SSHUserCADetailsDialog' import SSHUserCADetailsDialog from '../components/SSHUserCADetailsDialog'
import PageAlert from '../components/PageAlert'
import { api, SSHAccessProfile, SSHAccessProfileAdminUpsertPayload, SSHCredential, SSHPrincipalGrant, SSHServer, SSHServerGroup, SSHUserCA, User, UserGroup } from '../api' import { api, SSHAccessProfile, SSHAccessProfileAdminUpsertPayload, SSHCredential, SSHPrincipalGrant, SSHServer, SSHServerGroup, SSHUserCA, User, UserGroup } from '../api'
const emptyForm = (): SSHAccessProfileFormState => ({ const emptyForm = (): SSHAccessProfileFormState => ({
@@ -381,7 +382,7 @@ export default function AdminSSHAccessProfilesPage() {
</Button> </Button>
} }
> >
{error ? <Alert severity="error">{error}</Alert> : null} <PageAlert message={error} onClose={() => setError(null)} />
{!servers.length ? <Alert severity="info">Create an SSH server first.</Alert> : null} {!servers.length ? <Alert severity="info">Create an SSH server first.</Alert> : null}
{loading ? ( {loading ? (
<Typography variant="body2" color="text.secondary"> <Typography variant="body2" color="text.secondary">
@@ -20,6 +20,7 @@ import { ListRowActionButton, ListRowActions } from '../components/ListRowAction
import MonospaceTextField from '../components/MonospaceTextField' import MonospaceTextField from '../components/MonospaceTextField'
import SectionCard from '../components/SectionCard' import SectionCard from '../components/SectionCard'
import SSHCredentialDetailsDialog from '../components/SSHCredentialDetailsDialog' import SSHCredentialDetailsDialog from '../components/SSHCredentialDetailsDialog'
import PageAlert from '../components/PageAlert'
import { api, SSHCredential, SSHCredentialUpsertPayload } from '../api' import { api, SSHCredential, SSHCredentialUpsertPayload } from '../api'
import CompactListItemText from '../components/CompactListItemText' import CompactListItemText from '../components/CompactListItemText'
@@ -141,7 +142,7 @@ export default function AdminSSHCredentialsPage() {
</HeaderActionButton> </HeaderActionButton>
} }
> >
{error ? <Alert severity="error" sx={{ mb: 1 }}>{error}</Alert> : null} <PageAlert message={error} onClose={() => setError(null)} sx={{ mb: 1 }} />
<List> <List>
{items.map((item) => ( {items.map((item) => (
<ListItem key={item.id} divider sx={{ alignItems: 'flex-start' }}> <ListItem key={item.id} divider sx={{ alignItems: 'flex-start' }}>
@@ -179,7 +180,7 @@ export default function AdminSSHCredentialsPage() {
<Dialog open={dialogOpen} onClose={() => setDialogOpen(false)} maxWidth="md" fullWidth> <Dialog open={dialogOpen} onClose={() => setDialogOpen(false)} maxWidth="md" fullWidth>
<DialogTitle>{editingItem ? 'Edit SSH Credential' : 'Import SSH Credential'}</DialogTitle> <DialogTitle>{editingItem ? 'Edit SSH Credential' : 'Import SSH Credential'}</DialogTitle>
<FormDialogContent> <FormDialogContent>
{dialogError ? <Alert severity="error">{dialogError}</Alert> : null} <PageAlert message={dialogError} onClose={() => setDialogError(null)} />
<TextField label="Name" value={form.name} onChange={(event) => setForm((prev) => ({ ...prev, name: event.target.value }))} /> <TextField label="Name" value={form.name} onChange={(event) => setForm((prev) => ({ ...prev, name: event.target.value }))} />
<TextField label="Description" value={form.description} onChange={(event) => setForm((prev) => ({ ...prev, description: event.target.value }))} /> <TextField label="Description" value={form.description} onChange={(event) => setForm((prev) => ({ ...prev, description: event.target.value }))} />
{editingItem ? null : ( {editingItem ? null : (
@@ -24,6 +24,7 @@ import SSHPrincipalGrantFormDialog, { SSHPrincipalGrantFormState } from '../comp
import { api, SSHPrincipalGrant, SSHPrincipalGrantUpsertPayload, User, UserGroup } from '../api' import { api, SSHPrincipalGrant, SSHPrincipalGrantUpsertPayload, User, UserGroup } from '../api'
import SelectField from '../components/SelectField' import SelectField from '../components/SelectField'
import CompactListItemText from '../components/CompactListItemText' import CompactListItemText from '../components/CompactListItemText'
import PageAlert from '../components/PageAlert'
function fmt(value: number): string { function fmt(value: number): string {
if (!value || value <= 0) { if (!value || value <= 0) {
@@ -301,7 +302,7 @@ export default function AdminSSHPrincipalGrantsPage() {
</HeaderActionButton> </HeaderActionButton>
} }
> >
{error ? <Alert severity="error" sx={{ mb: 1 }}>{error}</Alert> : null} <PageAlert message={error} onClose={() => setError(null)} sx={{ mb: 1 }} />
{loading ? <Typography variant="body2" color="text.secondary">Loading principal grants...</Typography> : null} {loading ? <Typography variant="body2" color="text.secondary">Loading principal grants...</Typography> : null}
{!loading && items.length === 0 ? <Typography variant="body2" color="text.secondary">No principal grants configured.</Typography> : null} {!loading && items.length === 0 ? <Typography variant="body2" color="text.secondary">No principal grants configured.</Typography> : null}
{!loading ? ( {!loading ? (
+2 -2
View File
@@ -1,4 +1,3 @@
import Alert from '@mui/material/Alert'
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'
@@ -18,6 +17,7 @@ import SSHServerGroupFormDialog from '../components/SSHServerGroupFormDialog'
import { SSHServerGroupFormState } from '../components/SSHServerGroupFormDialog' import { SSHServerGroupFormState } from '../components/SSHServerGroupFormDialog'
import SSHServerGroupMembersDialog from '../components/SSHServerGroupMembersDialog' import SSHServerGroupMembersDialog from '../components/SSHServerGroupMembersDialog'
import SSHHostKeysDialog from '../components/SSHHostKeysDialog' import SSHHostKeysDialog from '../components/SSHHostKeysDialog'
import PageAlert from '../components/PageAlert'
import { api, SSHServer, SSHServerGroup, SSHServerGroupUpsertPayload, SSHServerHostKey, SSHServerUpsertPayload } from '../api' import { api, SSHServer, SSHServerGroup, SSHServerGroupUpsertPayload, SSHServerHostKey, SSHServerUpsertPayload } from '../api'
import CompactListItemText from '../components/CompactListItemText' import CompactListItemText from '../components/CompactListItemText'
@@ -429,7 +429,7 @@ export default function AdminSSHServersPage() {
</Button> </Button>
} }
> >
{error ? <Alert severity="error">{error}</Alert> : null} <PageAlert message={error} onClose={() => setError(null)} />
{loading ? ( {loading ? (
<Typography variant="body2" color="text.secondary"> <Typography variant="body2" color="text.secondary">
Loading... Loading...
+2 -2
View File
@@ -1,4 +1,3 @@
import Alert from '@mui/material/Alert'
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'
@@ -16,6 +15,7 @@ import SectionCard from '../components/SectionCard'
import SSHAccessProfileDetailsDialog from '../components/SSHAccessProfileDetailsDialog' import SSHAccessProfileDetailsDialog from '../components/SSHAccessProfileDetailsDialog'
import SSHServerDetailsDialog from '../components/SSHServerDetailsDialog' import SSHServerDetailsDialog from '../components/SSHServerDetailsDialog'
import SSHTranscriptDialog from '../components/SSHTranscriptDialog' import SSHTranscriptDialog from '../components/SSHTranscriptDialog'
import PageAlert from '../components/PageAlert'
import { api, SSHAccessProfile, SSHServer, SSHSession, SSHSessionListResponse } from '../api' import { api, SSHAccessProfile, SSHServer, SSHSession, SSHSessionListResponse } from '../api'
function fmt(value: number): string { function fmt(value: number): string {
@@ -159,7 +159,7 @@ export default function AdminSSHSessionsPage() {
return ( return (
<Box sx={{ display: 'grid', gap: 1 }}> <Box sx={{ display: 'grid', gap: 1 }}>
<Typography variant="h5">Admin: SSH Sessions</Typography> <Typography variant="h5">Admin: SSH Sessions</Typography>
{error ? <Alert severity="error">{error}</Alert> : null} <PageAlert message={error} onClose={() => setError(null)} />
<SectionCard <SectionCard
title="SSH Session History" title="SSH Session History"
subtitle="All SSH sessions across users." subtitle="All SSH sessions across users."
@@ -24,6 +24,7 @@ import SSHPrincipalGrantDetailsDialog from '../components/SSHPrincipalGrantDetai
import SSHCertificateInspectDialog from '../components/SSHCertificateInspectDialog' import SSHCertificateInspectDialog from '../components/SSHCertificateInspectDialog'
import { api, SSHPrincipalGrant, SSHUserCA, SSHUserCAIssuance } from '../api' import { api, SSHPrincipalGrant, SSHUserCA, SSHUserCAIssuance } from '../api'
import SelectField from '../components/SelectField' import SelectField from '../components/SelectField'
import PageAlert from '../components/PageAlert'
function fmt(value: number): string { function fmt(value: number): string {
if (!value || value <= 0) { return '-' } if (!value || value <= 0) { return '-' }
@@ -158,7 +159,7 @@ export default function AdminSSHSignHistoryPage() {
return ( return (
<Box> <Box>
<Typography variant="h5" sx={{ mb: 2 }}>Admin: SSH Signing History</Typography> <Typography variant="h5" sx={{ mb: 2 }}>Admin: SSH Signing History</Typography>
{error ? <Alert severity="error" sx={{ mb: 1 }}>{error}</Alert> : null} <PageAlert message={error} onClose={() => setError(null)} sx={{ mb: 1 }} />
<SectionCard title="SSH Signing History"> <SectionCard title="SSH Signing History">
<Box sx={{ display: 'flex', gap: 1, alignItems: 'center', flexWrap: 'wrap' }}> <Box sx={{ display: 'flex', gap: 1, alignItems: 'center', flexWrap: 'wrap' }}>
+3 -2
View File
@@ -23,6 +23,7 @@ import SectionCard from '../components/SectionCard'
import SSHUserCADetailsDialog from '../components/SSHUserCADetailsDialog' import SSHUserCADetailsDialog from '../components/SSHUserCADetailsDialog'
import SSHUserCAFormDialog, { SSHUserCAFormState } from '../components/SSHUserCAFormDialog' import SSHUserCAFormDialog, { SSHUserCAFormState } from '../components/SSHUserCAFormDialog'
import SSHCertificateInspectDialog from '../components/SSHCertificateInspectDialog' import SSHCertificateInspectDialog from '../components/SSHCertificateInspectDialog'
import PageAlert from '../components/PageAlert'
import { api, SSHSignUserKeyResponse, SSHUserCA, SSHUserCACreatePayload, SSHUserCASignPayload, SSHUserCAUpdatePayload } from '../api' import { api, SSHSignUserKeyResponse, SSHUserCA, SSHUserCACreatePayload, SSHUserCASignPayload, SSHUserCAUpdatePayload } from '../api'
import CompactListItemText from '../components/CompactListItemText' import CompactListItemText from '../components/CompactListItemText'
@@ -310,7 +311,7 @@ export default function AdminSSHUserCAPage() {
</HeaderActionButton> </HeaderActionButton>
} }
> >
{error ? <Alert severity="error" sx={{ mb: 1 }}>{error}</Alert> : null} <PageAlert message={error} onClose={() => setError(null)} sx={{ mb: 1 }} />
{loading ? ( {loading ? (
<Typography variant="body2" color="text.secondary">Loading SSH User CAs...</Typography> <Typography variant="body2" color="text.secondary">Loading SSH User CAs...</Typography>
) : ( ) : (
@@ -408,7 +409,7 @@ export default function AdminSSHUserCAPage() {
<DialogTitle> <DialogTitle>
<Box sx={{ display: 'grid', gap: 1 }}> <Box sx={{ display: 'grid', gap: 1 }}>
<Typography variant="h6">Sign SSH User Key</Typography> <Typography variant="h6">Sign SSH User Key</Typography>
{dialogError ? <Alert severity="error">{dialogError}</Alert> : null} <PageAlert message={dialogError} onClose={() => setDialogError(null)} />
</Box> </Box>
</DialogTitle> </DialogTitle>
<FormDialogContent> <FormDialogContent>
@@ -21,6 +21,7 @@ import {
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { ListRowActionButton, ListRowActions } from '../components/ListRowActions' import { ListRowActionButton, ListRowActions } from '../components/ListRowActions'
import SectionCard from '../components/SectionCard' import SectionCard from '../components/SectionCard'
import PageAlert from '../components/PageAlert'
import { api, CertPrincipalBinding, PKICert, PKICertDetail, PrincipalAPIKey, PrincipalProjectRole, Project, ServicePrincipal } from '../api' import { api, CertPrincipalBinding, PKICert, PKICertDetail, PrincipalAPIKey, PrincipalProjectRole, Project, ServicePrincipal } from '../api'
import SelectField from '../components/SelectField' import SelectField from '../components/SelectField'
import CompactListItemText from '../components/CompactListItemText' import CompactListItemText from '../components/CompactListItemText'
@@ -552,7 +553,7 @@ export default function AdminServicePrincipalsPage() {
return ( return (
<Box> <Box>
<Typography variant="h5" sx={{ mb: 2 }}>Admin: Service Principals</Typography> <Typography variant="h5" sx={{ mb: 2 }}>Admin: Service Principals</Typography>
{error ? <Alert severity="error" sx={{ mb: 1 }}>{error}</Alert> : null} <PageAlert message={error} onClose={() => setError(null)} sx={{ mb: 1 }} />
<Box sx={{ display: 'grid', gap: 1 }}> <Box sx={{ display: 'grid', gap: 1 }}>
<SectionCard <SectionCard
collapsible collapsible
@@ -613,7 +614,7 @@ export default function AdminServicePrincipalsPage() {
title={`Project Role Assignments (${filteredRoleAssignmentCount})`} title={`Project Role Assignments (${filteredRoleAssignmentCount})`}
subtitle="Define what each principal can do per project." subtitle="Define what each principal can do per project."
> >
{dialogError ? <Alert severity="error" sx={{ mb: 1 }}>{dialogError}</Alert> : null} <PageAlert message={dialogError} onClose={() => setDialogError(null)} sx={{ mb: 1 }} />
<Box sx={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr auto', gap: 1, mb: 1 }}> <Box sx={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr auto', gap: 1, mb: 1 }}>
<SelectField select label="Principal" value={rolePrincipalID} onChange={(event) => setRolePrincipalID(event.target.value)} size="small"> <SelectField select label="Principal" value={rolePrincipalID} onChange={(event) => setRolePrincipalID(event.target.value)} size="small">
{principals.map((item) => ( {principals.map((item) => (
@@ -782,7 +783,7 @@ export default function AdminServicePrincipalsPage() {
<DialogTitle> <DialogTitle>
<Box sx={{ display: 'grid', gap: 1 }}> <Box sx={{ display: 'grid', gap: 1 }}>
<Typography variant="h6">New Service Principal</Typography> <Typography variant="h6">New Service Principal</Typography>
{dialogError ? <Alert severity="error">{dialogError}</Alert> : null} <PageAlert message={dialogError} onClose={() => setDialogError(null)} />
</Box> </Box>
</DialogTitle> </DialogTitle>
<FormDialogContent> <FormDialogContent>
@@ -821,7 +822,7 @@ export default function AdminServicePrincipalsPage() {
<DialogTitle> <DialogTitle>
<Box sx={{ display: 'grid', gap: 1 }}> <Box sx={{ display: 'grid', gap: 1 }}>
<Typography variant="h6">Add Cert Binding</Typography> <Typography variant="h6">Add Cert Binding</Typography>
{dialogError ? <Alert severity="error">{dialogError}</Alert> : null} <PageAlert message={dialogError} onClose={() => setDialogError(null)} />
</Box> </Box>
</DialogTitle> </DialogTitle>
<FormDialogContent> <FormDialogContent>
@@ -876,7 +877,7 @@ export default function AdminServicePrincipalsPage() {
<DialogTitle> <DialogTitle>
<Box sx={{ display: 'grid', gap: 1 }}> <Box sx={{ display: 'grid', gap: 1 }}>
<Typography variant="h6">API Keys{keysPrincipal ? `: ${keysPrincipal.name}` : ''}</Typography> <Typography variant="h6">API Keys{keysPrincipal ? `: ${keysPrincipal.name}` : ''}</Typography>
{keysError ? <Alert severity="error">{keysError}</Alert> : null} <PageAlert message={keysError} onClose={() => setKeysError(null)} />
</Box> </Box>
</DialogTitle> </DialogTitle>
<FormDialogContent> <FormDialogContent>
@@ -1,4 +1,3 @@
import Alert from '@mui/material/Alert'
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 Checkbox from '@mui/material/Checkbox' import Checkbox from '@mui/material/Checkbox'
@@ -17,6 +16,7 @@ import ConfirmDeleteDialog from '../components/ConfirmDeleteDialog'
import FormDialogContent from '../components/FormDialogContent' import FormDialogContent from '../components/FormDialogContent'
import { ListRowActionButton, ListRowActions } from '../components/ListRowActions' import { ListRowActionButton, ListRowActions } from '../components/ListRowActions'
import SectionCard from '../components/SectionCard' import SectionCard from '../components/SectionCard'
import PageAlert from '../components/PageAlert'
import { api, PKICert, TLSAuthPolicy } from '../api' import { api, PKICert, TLSAuthPolicy } from '../api'
import SelectField from '../components/SelectField' import SelectField from '../components/SelectField'
@@ -202,7 +202,7 @@ export default function AdminTLSAuthPoliciesPage() {
</Button> </Button>
} }
> >
{error ? <Alert severity="error">{error}</Alert> : null} <PageAlert message={error} onClose={() => setError(null)} />
{loading ? ( {loading ? (
<Typography variant="body2" color="text.secondary"> <Typography variant="body2" color="text.secondary">
Loading... Loading...
@@ -256,7 +256,7 @@ export default function AdminTLSAuthPoliciesPage() {
<DialogTitle> <DialogTitle>
<Box sx={{ display: 'grid', gap: 1 }}> <Box sx={{ display: 'grid', gap: 1 }}>
<Typography variant="h6">{editingID ? 'Edit TLS Auth Policy' : 'New TLS Auth Policy'}</Typography> <Typography variant="h6">{editingID ? 'Edit TLS Auth Policy' : 'New TLS Auth Policy'}</Typography>
{dialogError ? <Alert severity="error">{dialogError}</Alert> : null} <PageAlert message={dialogError} onClose={() => setDialogError(null)} />
</Box> </Box>
</DialogTitle> </DialogTitle>
<FormDialogContent> <FormDialogContent>
+3 -2
View File
@@ -18,6 +18,7 @@ import {
import { useEffect, useMemo, useState } from 'react' import { useEffect, useMemo, useState } from 'react'
import { ListRowActionButton, ListRowActions } from '../components/ListRowActions' import { ListRowActionButton, ListRowActions } from '../components/ListRowActions'
import SectionCard from '../components/SectionCard' import SectionCard from '../components/SectionCard'
import PageAlert from '../components/PageAlert'
import { api, PKICA, PKICert, TLSAuthPolicy, TLSListener, TLSSettings } from '../api' import { api, PKICA, PKICert, TLSAuthPolicy, TLSListener, TLSSettings } from '../api'
import SelectField from '../components/SelectField' import SelectField from '../components/SelectField'
@@ -395,7 +396,7 @@ export default function AdminTLSSettingsPage() {
</Button> </Button>
} }
> >
{error ? <Alert severity="error" sx={{ mb: 1 }}>{error}</Alert> : null} <PageAlert message={error} onClose={() => setError(null)} sx={{ mb: 1 }} />
{(loading || listenersLoading) ? ( {(loading || listenersLoading) ? (
<Typography variant="body2" color="text.secondary" sx={{ mb: 1 }}> <Typography variant="body2" color="text.secondary" sx={{ mb: 1 }}>
Loading... Loading...
@@ -502,7 +503,7 @@ export default function AdminTLSSettingsPage() {
<DialogTitle> <DialogTitle>
<Box sx={{ display: 'grid', gap: 1 }}> <Box sx={{ display: 'grid', gap: 1 }}>
<Typography variant="h6">{editingMain ? 'Edit Main Listener' : editingID ? 'Edit Listener' : 'New Listener'}</Typography> <Typography variant="h6">{editingMain ? 'Edit Main Listener' : editingID ? 'Edit Listener' : 'New Listener'}</Typography>
{listenerError ? <Alert severity="error">{listenerError}</Alert> : null} <PageAlert message={listenerError} onClose={() => setListenerError(null)} />
{editingMain ? ( {editingMain ? (
<Alert severity="info">Main listener transport changes are saved now but require backend restart to take effect.</Alert> <Alert severity="info">Main listener transport changes are saved now but require backend restart to take effect.</Alert>
) : null} ) : null}
+3 -2
View File
@@ -26,6 +26,7 @@ import {
import { useEffect, useMemo, useState } from 'react' import { useEffect, useMemo, useState } from 'react'
import SectionCard from '../components/SectionCard' import SectionCard from '../components/SectionCard'
import TintedPanel from '../components/TintedPanel' import TintedPanel from '../components/TintedPanel'
import PageAlert from '../components/PageAlert'
import { api, SubjectPermission, User, UserGroup, UserGroupMember, UserGroupUpsertPayload } from '../api' import { api, SubjectPermission, User, UserGroup, UserGroupMember, UserGroupUpsertPayload } from '../api'
import SelectField from '../components/SelectField' import SelectField from '../components/SelectField'
@@ -298,7 +299,7 @@ export default function AdminUserGroupsPage() {
<Typography variant="h5">Admin: User Groups</Typography> <Typography variant="h5">Admin: User Groups</Typography>
<Button variant="outlined" startIcon={<AddIcon />} onClick={openCreate}>New Group</Button> <Button variant="outlined" startIcon={<AddIcon />} onClick={openCreate}>New Group</Button>
</Box> </Box>
{error ? <Alert severity="error" sx={{ mb: 1 }}>{error}</Alert> : null} <PageAlert message={error} onClose={() => setError(null)} sx={{ mb: 1 }} />
<Box sx={{ display: 'grid', gridTemplateColumns: { xs: '1fr', md: '40% minmax(0, 1fr)' }, gap: 1, alignItems: 'start' }}> <Box sx={{ display: 'grid', gridTemplateColumns: { xs: '1fr', md: '40% minmax(0, 1fr)' }, gap: 1, alignItems: 'start' }}>
<TintedPanel> <TintedPanel>
<TextField <TextField
@@ -501,7 +502,7 @@ export default function AdminUserGroupsPage() {
<Dialog open={editOpen} onClose={() => setEditOpen(false)} maxWidth="sm" fullWidth> <Dialog open={editOpen} onClose={() => setEditOpen(false)} maxWidth="sm" fullWidth>
<DialogTitle>{editID ? 'Edit Group' : 'New Group'}</DialogTitle> <DialogTitle>{editID ? 'Edit Group' : 'New Group'}</DialogTitle>
<FormDialogContent> <FormDialogContent>
{dialogError ? <Alert severity="error">{dialogError}</Alert> : null} <PageAlert message={dialogError} onClose={() => setDialogError(null)} />
<TextField label="Name" value={name} onChange={(event) => setName(event.target.value)} /> <TextField label="Name" value={name} onChange={(event) => setName(event.target.value)} />
<TextField <TextField
label="Description" label="Description"
+2 -1
View File
@@ -15,6 +15,7 @@ import SectionCard from '../components/SectionCard'
import UserFormDialog, { UserFormState } from '../components/UserFormDialog' import UserFormDialog, { UserFormState } from '../components/UserFormDialog'
import { api, SubjectPermission, User, UserCreatePayload, UserUpdatePayload } from '../api' import { api, SubjectPermission, User, UserCreatePayload, UserUpdatePayload } from '../api'
import CompactListItemText from '../components/CompactListItemText' import CompactListItemText from '../components/CompactListItemText'
import PageAlert from '../components/PageAlert'
const projectCreatePermission = 'project.create' const projectCreatePermission = 'project.create'
@@ -257,7 +258,7 @@ export default function AdminUsersPage() {
</HeaderActionButton> </HeaderActionButton>
} }
> >
{error ? <Alert severity="error" sx={{ mb: 1 }}>{error}</Alert> : null} <PageAlert message={error} onClose={() => setError(null)} sx={{ mb: 1 }} />
<List> <List>
{users.map((u) => ( {users.map((u) => (
<ListItem <ListItem
+2 -1
View File
@@ -23,6 +23,7 @@ import { ListRowActionButton, ListRowActions } from '../components/ListRowAction
import SectionCard from '../components/SectionCard' import SectionCard from '../components/SectionCard'
import { api, APIKey } from '../api' import { api, APIKey } from '../api'
import CompactListItemText from '../components/CompactListItemText' import CompactListItemText from '../components/CompactListItemText'
import PageAlert from '../components/PageAlert'
function formatUnix(value: number) { function formatUnix(value: number) {
if (!value || value <= 0) { if (!value || value <= 0) {
@@ -181,7 +182,7 @@ export default function ApiKeysPage() {
</Button> </Button>
} }
> >
{error ? <Alert severity="error" sx={{ mb: 1 }}>{error}</Alert> : null} <PageAlert message={error} onClose={() => setError(null)} sx={{ mb: 1 }} />
{loading ? ( {loading ? (
<Typography variant="body2" color="text.secondary">Loading API keys...</Typography> <Typography variant="body2" color="text.secondary">Loading API keys...</Typography>
) : ( ) : (
@@ -2,7 +2,6 @@ import DownloadIcon from '@mui/icons-material/Download'
import KeyIcon from '@mui/icons-material/Key' import KeyIcon from '@mui/icons-material/Key'
import BlockIcon from '@mui/icons-material/Block' import BlockIcon from '@mui/icons-material/Block'
import VisibilityIcon from '@mui/icons-material/Visibility' import VisibilityIcon from '@mui/icons-material/Visibility'
import Alert from '@mui/material/Alert'
import ConfirmDeleteDialog from '../components/ConfirmDeleteDialog' import ConfirmDeleteDialog from '../components/ConfirmDeleteDialog'
import { import {
Box, Box,
@@ -21,6 +20,7 @@ import PKIClientProfileDetailsDialog from '../components/PKIClientProfileDetails
import PKICADetailsDialog from '../components/PKICADetailsDialog' import PKICADetailsDialog from '../components/PKICADetailsDialog'
import PKICertDetailsDialog from '../components/PKICertDetailsDialog' import PKICertDetailsDialog from '../components/PKICertDetailsDialog'
import SectionCard from '../components/SectionCard' import SectionCard from '../components/SectionCard'
import PageAlert from '../components/PageAlert'
import { api, PKICADetail, PKIClientIssueResponse, PKIClientIssuance, PKIClientProfile, PKICertDetail } from '../api' import { api, PKICADetail, PKIClientIssueResponse, PKIClientIssuance, PKIClientProfile, PKICertDetail } from '../api'
import CompactListItemText from '../components/CompactListItemText' import CompactListItemText from '../components/CompactListItemText'
@@ -319,7 +319,7 @@ export default function ClientCertificatesPage() {
return ( return (
<Box sx={{ display: 'grid', gap: 1 }}> <Box sx={{ display: 'grid', gap: 1 }}>
<Typography variant="h5">Client Certificates</Typography> <Typography variant="h5">Client Certificates</Typography>
{error ? <Alert severity="error">{error}</Alert> : null} <PageAlert message={error} onClose={() => setError(null)} />
<SectionCard <SectionCard
collapsible collapsible
+7 -7
View File
@@ -5,7 +5,6 @@ import SecurityIcon from '@mui/icons-material/Security'
import StorageIcon from '@mui/icons-material/Storage' import StorageIcon from '@mui/icons-material/Storage'
import VerifiedUserIcon from '@mui/icons-material/VerifiedUser' import VerifiedUserIcon from '@mui/icons-material/VerifiedUser'
import WorkspacesIcon from '@mui/icons-material/Workspaces' import WorkspacesIcon from '@mui/icons-material/Workspaces'
import Alert from '@mui/material/Alert'
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'
@@ -28,6 +27,7 @@ import {
User User
} from '../api' } from '../api'
import SectionCard from '../components/SectionCard' import SectionCard from '../components/SectionCard'
import PageAlert from '../components/PageAlert'
type LayoutContext = { type LayoutContext = {
user: User | null user: User | null
@@ -631,7 +631,7 @@ export default function DashboardPage() {
</DashboardCard> </DashboardCard>
<DashboardCard title="Recent Projects" subtitle="Most recently updated visible projects"> <DashboardCard title="Recent Projects" subtitle="Most recently updated visible projects">
{projectsError ? <Alert severity="error">{projectsError}</Alert> : null} <PageAlert message={projectsError} onClose={() => setProjectsError(null)} />
{projectsLoading ? <Typography variant="body2" color="text.secondary">Loading projects...</Typography> : null} {projectsLoading ? <Typography variant="body2" color="text.secondary">Loading projects...</Typography> : null}
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}> <Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
<Metric label="Visible Projects" value={String(projectCount)} /> <Metric label="Visible Projects" value={String(projectCount)} />
@@ -682,7 +682,7 @@ export default function DashboardPage() {
</DashboardCard> </DashboardCard>
<DashboardCard title="My Credentials" subtitle="Keys and certificates that need attention"> <DashboardCard title="My Credentials" subtitle="Keys and certificates that need attention">
{credentialsError ? <Alert severity="error">{credentialsError}</Alert> : null} <PageAlert message={credentialsError} onClose={() => setCredentialsError(null)} />
{credentialsLoading ? <Typography variant="body2" color="text.secondary">Loading credentials...</Typography> : null} {credentialsLoading ? <Typography variant="body2" color="text.secondary">Loading credentials...</Typography> : null}
<Box sx={{ display: 'grid', gridTemplateColumns: { xs: '1fr', sm: 'repeat(3, 1fr)' }, gap: 1 }}> <Box sx={{ display: 'grid', gridTemplateColumns: { xs: '1fr', sm: 'repeat(3, 1fr)' }, gap: 1 }}>
<Metric label="Active API Keys" value={String(activeAPIKeys.length)} helper={`${expiringAPIKeys.length} expiring soon`} /> <Metric label="Active API Keys" value={String(activeAPIKeys.length)} helper={`${expiringAPIKeys.length} expiring soon`} />
@@ -705,7 +705,7 @@ export default function DashboardPage() {
{user?.is_admin ? ( {user?.is_admin ? (
<Box sx={{ display: 'grid', gridTemplateColumns: { xs: '1fr', md: '1fr 1fr', xl: '1fr 1fr 1fr' }, gap: 1 }}> <Box sx={{ display: 'grid', gridTemplateColumns: { xs: '1fr', md: '1fr 1fr', xl: '1fr 1fr 1fr' }, gap: 1 }}>
<DashboardCard title="Repository Summary" subtitle="Global repository counts"> <DashboardCard title="Repository Summary" subtitle="Global repository counts">
{reposError ? <Alert severity="error">{reposError}</Alert> : null} <PageAlert message={reposError} onClose={() => setReposError(null)} />
{reposLoading ? <Typography variant="body2" color="text.secondary">Loading repositories...</Typography> : null} {reposLoading ? <Typography variant="body2" color="text.secondary">Loading repositories...</Typography> : null}
<Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 1 }}> <Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 1 }}>
<Metric label="Projects" value={String(projectCount)} /> <Metric label="Projects" value={String(projectCount)} />
@@ -716,7 +716,7 @@ export default function DashboardPage() {
</DashboardCard> </DashboardCard>
<DashboardCard title="Certificate Warnings" subtitle="Expiring certificates and ACME order state"> <DashboardCard title="Certificate Warnings" subtitle="Expiring certificates and ACME order state">
{certsError ? <Alert severity="error">{certsError}</Alert> : null} <PageAlert message={certsError} onClose={() => setCertsError(null)} />
{certsLoading ? <Typography variant="body2" color="text.secondary">Loading certificate state...</Typography> : null} {certsLoading ? <Typography variant="body2" color="text.secondary">Loading certificate state...</Typography> : null}
<Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 1 }}> <Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 1 }}>
<Metric label="Expiring PKI Certs" value={String(expiringPKICerts.length)} helper="within 30 days" /> <Metric label="Expiring PKI Certs" value={String(expiringPKICerts.length)} helper="within 30 days" />
@@ -744,7 +744,7 @@ export default function DashboardPage() {
</DashboardCard> </DashboardCard>
<DashboardCard title="Mirror Status" subtitle="RPM mirror directories and sync state"> <DashboardCard title="Mirror Status" subtitle="RPM mirror directories and sync state">
{mirrorsError ? <Alert severity="error">{mirrorsError}</Alert> : null} <PageAlert message={mirrorsError} onClose={() => setMirrorsError(null)} />
{mirrorsLoading ? <Typography variant="body2" color="text.secondary">Loading mirror status...</Typography> : null} {mirrorsLoading ? <Typography variant="body2" color="text.secondary">Loading mirror status...</Typography> : null}
<Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 1 }}> <Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 1 }}>
<Metric label="Mirrors" value={String(rpmMirrors.length)} /> <Metric label="Mirrors" value={String(rpmMirrors.length)} />
@@ -814,7 +814,7 @@ export default function DashboardPage() {
</DashboardCard> </DashboardCard>
<DashboardCard title="System Health" subtitle="Listener and TLS summary"> <DashboardCard title="System Health" subtitle="Listener and TLS summary">
{tlsError ? <Alert severity="error">{tlsError}</Alert> : null} <PageAlert message={tlsError} onClose={() => setTLSError(null)} />
{tlsLoading ? <Typography variant="body2" color="text.secondary">Loading TLS health...</Typography> : null} {tlsLoading ? <Typography variant="body2" color="text.secondary">Loading TLS health...</Typography> : null}
<Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 1 }}> <Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 1 }}>
<Metric label="Main HTTP / HTTPS" value={`${tlsSettings?.http_addrs?.length || 0} / ${tlsSettings?.https_addrs?.length || 0}`} /> <Metric label="Main HTTP / HTTPS" value={`${tlsSettings?.http_addrs?.length || 0} / ${tlsSettings?.https_addrs?.length || 0}`} />
+3 -6
View File
@@ -1,8 +1,9 @@
import { Alert, Box, Button, Divider, MenuItem, Paper, TextField, Typography } from '@mui/material' import { Box, Button, Divider, MenuItem, Paper, TextField, Typography } from '@mui/material'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import { api, Repo } from '../api' import { api, Repo } from '../api'
import Autocomplete from '../components/Autocomplete' import Autocomplete from '../components/Autocomplete'
import PageAlert from '../components/PageAlert'
import SectionCard from '../components/SectionCard' import SectionCard from '../components/SectionCard'
import SelectField from '../components/SelectField' import SelectField from '../components/SelectField'
@@ -87,11 +88,7 @@ export default function GlobalReposPage() {
</Box> </Box>
} }
> >
{reposError ? ( <PageAlert message={reposError} onClose={() => setReposError(null)} />
<Alert severity="error">
{reposError}
</Alert>
) : null}
<Box sx={{ display: 'grid', gap: 0.75 }}> <Box sx={{ display: 'grid', gap: 0.75 }}>
{reposLoading ? ( {reposLoading ? (
<Typography variant="body2" color="text.secondary"> <Typography variant="body2" color="text.secondary">
+1 -1
View File
@@ -50,7 +50,7 @@ export default function LoginPage() {
return ( return (
<Paper sx={{ p: 4, maxWidth: 420, mx: 'auto', mt: 12, backgroundColor: 'transparent' }}> <Paper sx={{ p: 4, maxWidth: 420, mx: 'auto', mt: 12, backgroundColor: 'transparent' }}>
<Typography variant="h6" gutterBottom> <Typography variant="h6" gutterBottom>
Sign in to {brand.server_title} Sign in to {brand.site_name}
</Typography> </Typography>
<Box component="form" onSubmit={handleLogin}> <Box component="form" onSubmit={handleLogin}>
<TextField name="username" label="Username" fullWidth margin="normal" value={username} onChange={(event) => setUsername(event.target.value)} /> <TextField name="username" label="Username" fullWidth margin="normal" value={username} onChange={(event) => setUsername(event.target.value)} />
+2 -1
View File
@@ -16,6 +16,7 @@ import ConfirmDeleteDialog from '../components/ConfirmDeleteDialog'
import ProjectFormDialog from '../components/ProjectFormDialog' import ProjectFormDialog from '../components/ProjectFormDialog'
import { ProjectDescriptionTab, ProjectHomePageValue } from '../components/ProjectFormDialog' import { ProjectDescriptionTab, ProjectHomePageValue } from '../components/ProjectFormDialog'
import SelectField from '../components/SelectField' import SelectField from '../components/SelectField'
import PageAlert from '../components/PageAlert'
export default function ProjectHomePage() { export default function ProjectHomePage() {
const { projectId } = useParams() const { projectId } = useParams()
@@ -435,7 +436,7 @@ export default function ProjectHomePage() {
) : undefined ) : undefined
} }
> >
{membersError ? <Alert severity="error" sx={{ mb: 1 }}>{membersError}</Alert> : null} <PageAlert message={membersError} onClose={() => setMembersError(null)} sx={{ mb: 1 }} />
<Box sx={{ display: 'grid', gap: 0.75 }}> <Box sx={{ display: 'grid', gap: 0.75 }}>
{members.map((member) => ( {members.map((member) => (
<Box <Box
+2 -2
View File
@@ -1,11 +1,11 @@
import AddIcon from '@mui/icons-material/Add' import AddIcon from '@mui/icons-material/Add'
import DeleteIcon from '@mui/icons-material/Delete' import DeleteIcon from '@mui/icons-material/Delete'
import EditIcon from '@mui/icons-material/Edit' import EditIcon from '@mui/icons-material/Edit'
import Alert from '@mui/material/Alert'
import ConfirmDeleteDialog from '../components/ConfirmDeleteDialog' import ConfirmDeleteDialog from '../components/ConfirmDeleteDialog'
import { Box, Button, Chip, DialogContent, List, ListItem, ListItemText, Paper, TextField, Typography } from '@mui/material' import { Box, Button, Chip, DialogContent, List, ListItem, ListItemText, Paper, TextField, Typography } from '@mui/material'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import PageAlert from '../components/PageAlert'
import { api, Project, ProjectCreatePayload, ProjectUpdatePayload, User } from '../api' import { api, Project, ProjectCreatePayload, ProjectUpdatePayload, User } from '../api'
import Autocomplete from '../components/Autocomplete' import Autocomplete from '../components/Autocomplete'
import HeaderActionButton from '../components/HeaderActionButton' import HeaderActionButton from '../components/HeaderActionButton'
@@ -247,7 +247,7 @@ export default function ProjectsPage() {
) : undefined ) : undefined
} }
> >
{projectsError ? <Alert severity="error">{projectsError}</Alert> : null} <PageAlert message={projectsError} onClose={() => setProjectsError(null)} />
{projectsLoading ? ( {projectsLoading ? (
<Typography variant="body2" color="text.secondary" sx={{ mb: 1 }}> <Typography variant="body2" color="text.secondary" sx={{ mb: 1 }}>
Loading projects... Loading projects...
+4 -14
View File
@@ -1,5 +1,4 @@
import { import {
Alert,
Box, Box,
Button, Button,
Divider, Divider,
@@ -17,6 +16,7 @@ import { api, DockerManifestDetail, DockerTagInfo, Project, Repo } from '../api'
import ProjectNavBar from '../components/ProjectNavBar' import ProjectNavBar from '../components/ProjectNavBar'
import RepoSubNav from '../components/RepoSubNav' import RepoSubNav from '../components/RepoSubNav'
import TintedPanel from '../components/TintedPanel' import TintedPanel from '../components/TintedPanel'
import PageAlert from '../components/PageAlert'
import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline' import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline'
import DriveFileRenameOutlineIcon from '@mui/icons-material/DriveFileRenameOutline' import DriveFileRenameOutlineIcon from '@mui/icons-material/DriveFileRenameOutline'
import ConfirmDeleteDialog from '../components/ConfirmDeleteDialog' import ConfirmDeleteDialog from '../components/ConfirmDeleteDialog'
@@ -323,11 +323,7 @@ export default function RepoDockerDetailPage(props: RepoDockerDetailPageProps) {
) : null} ) : null}
<Box sx={{ display: 'grid', gridTemplateColumns: '280px minmax(0, 1fr)', gap: 1, mb: 1 }}> <Box sx={{ display: 'grid', gridTemplateColumns: '280px minmax(0, 1fr)', gap: 1, mb: 1 }}>
<TintedPanel> <TintedPanel>
{imagesError ? ( <PageAlert severity="warning" message={imagesError} onClose={() => setImagesError(null)} sx={{ mb: 1 }} />
<Alert severity="warning" sx={{ mb: 1 }}>
{imagesError}
</Alert>
) : null}
<List dense> <List dense>
{images.map((image) => ( {images.map((image) => (
<ListItem key={image || 'root'} disablePadding> <ListItem key={image || 'root'} disablePadding>
@@ -384,11 +380,7 @@ export default function RepoDockerDetailPage(props: RepoDockerDetailPageProps) {
</List> </List>
</TintedPanel> </TintedPanel>
<TintedPanel> <TintedPanel>
{tagsError ? ( <PageAlert severity="warning" message={tagsError} onClose={() => setTagsError(null)} sx={{ mb: 1 }} />
<Alert severity="warning" sx={{ mb: 1 }}>
{tagsError}
</Alert>
) : null}
{selectedImage !== '' || images.includes('') ? ( {selectedImage !== '' || images.includes('') ? (
<Box sx={{ mb: 1 }}> <Box sx={{ mb: 1 }}>
<Typography variant="body2" color="text.secondary"> <Typography variant="body2" color="text.secondary">
@@ -446,9 +438,7 @@ export default function RepoDockerDetailPage(props: RepoDockerDetailPageProps) {
Loading manifest... Loading manifest...
</Typography> </Typography>
) : null} ) : null}
{detailError ? ( <PageAlert severity="warning" message={detailError} onClose={() => setDetailError(null)} />
<Alert severity="warning">{detailError}</Alert>
) : null}
{detail ? ( {detail ? (
<Box sx={{ display: 'grid', gap: 0.5 }}> <Box sx={{ display: 'grid', gap: 0.5 }}>
<Typography variant="body2">Tag: {detail.reference}</Typography> <Typography variant="body2">Tag: {detail.reference}</Typography>
+3 -6
View File
@@ -1,4 +1,4 @@
import { Alert, Box, Button, Divider, IconButton, List, ListItem, ListItemButton, ListItemText, Paper, Popover, Tab, Tabs, TextField, Tooltip, Typography } from '@mui/material' import { Box, Button, Divider, IconButton, List, ListItem, ListItemButton, ListItemText, Paper, Popover, Tab, Tabs, TextField, Tooltip, Typography } from '@mui/material'
import { lazy, Suspense, useEffect, useRef, useState } from 'react' import { lazy, Suspense, useEffect, useRef, useState } from 'react'
import { Link, useParams, useSearchParams } from 'react-router-dom' import { Link, useParams, useSearchParams } from 'react-router-dom'
import { api, Project, Repo, RepoCommit, RepoTreeEntry } from '../api' import { api, Project, Repo, RepoCommit, RepoTreeEntry } from '../api'
@@ -12,6 +12,7 @@ import ChevronRightIcon from '@mui/icons-material/ChevronRight'
import FileDownloadIcon from '@mui/icons-material/FileDownload' import FileDownloadIcon from '@mui/icons-material/FileDownload'
import OpenInNewIcon from '@mui/icons-material/OpenInNew' import OpenInNewIcon from '@mui/icons-material/OpenInNew'
import LazyCodeBlock from '../components/LazyCodeBlock' import LazyCodeBlock from '../components/LazyCodeBlock'
import PageAlert from '../components/PageAlert'
import ProjectNavBar from '../components/ProjectNavBar' import ProjectNavBar from '../components/ProjectNavBar'
import RepoSubNav from '../components/RepoSubNav' import RepoSubNav from '../components/RepoSubNav'
import TintedPanel from '../components/TintedPanel' import TintedPanel from '../components/TintedPanel'
@@ -710,11 +711,7 @@ export default function RepoGitDetailPage(props: RepoGitDetailPageProps) {
<ChevronLeftIcon fontSize="small" /> <ChevronLeftIcon fontSize="small" />
</IconButton> </IconButton>
</Box> </Box>
{treeError ? ( <PageAlert severity="warning" message={treeError} onClose={() => setTreeError(null)} sx={{ mb: 1 }} />
<Alert severity="warning" sx={{ mb: 1 }}>
{treeError}
</Alert>
) : null}
<List dense> <List dense>
<Box sx={{ px: 1, pb: 1 }}> <Box sx={{ px: 1, pb: 1 }}>
<TextField <TextField
+9 -19
View File
@@ -1,6 +1,5 @@
import FormDialogContent from '../components/FormDialogContent' import FormDialogContent from '../components/FormDialogContent'
import { import {
Alert,
Box, Box,
Button, Button,
Checkbox, Checkbox,
@@ -25,6 +24,7 @@ import {
} from '@mui/material' } from '@mui/material'
import { useEffect, useRef, useState } from 'react' import { useEffect, useRef, useState } from 'react'
import { Link, useParams, useSearchParams } from 'react-router-dom' import { Link, useParams, useSearchParams } from 'react-router-dom'
import PageAlert from '../components/PageAlert'
import { api, Project, Repo, RpmMirrorRun, RpmPackageDetail, RpmPackageSummary, RpmSubdirCreatePayload, RpmSubdirUpdatePayload, RpmTreeEntry } from '../api' import { api, Project, Repo, RpmMirrorRun, RpmPackageDetail, RpmPackageSummary, RpmSubdirCreatePayload, RpmSubdirUpdatePayload, RpmTreeEntry } from '../api'
import ChevronLeftIcon from '@mui/icons-material/ChevronLeft' import ChevronLeftIcon from '@mui/icons-material/ChevronLeft'
import ChevronRightIcon from '@mui/icons-material/ChevronRight' import ChevronRightIcon from '@mui/icons-material/ChevronRight'
@@ -897,11 +897,7 @@ export default function RepoRpmDetailPage(props: RepoRpmDetailPageProps) {
fullWidth fullWidth
sx={{ mb: 1, px: 0.5 }} sx={{ mb: 1, px: 0.5 }}
/> />
{rpmTreeError ? ( <PageAlert severity="warning" message={rpmTreeError} onClose={() => setRpmTreeError(null)} sx={{ mb: 1 }} />
<Alert severity="warning" sx={{ mb: 1 }}>
{rpmTreeError}
</Alert>
) : null}
<List dense> <List dense>
{rpmPath ? ( {rpmPath ? (
<ListItem disablePadding> <ListItem disablePadding>
@@ -1154,10 +1150,8 @@ export default function RepoRpmDetailPage(props: RepoRpmDetailPageProps) {
)} )}
</Box> </Box>
) : null} ) : null}
{!rpmDetail && !rpmDetailLoading && rpmError ? ( {!rpmDetail && !rpmDetailLoading ? (
<Alert severity="warning" sx={{ mt: 1 }}> <PageAlert severity="warning" message={rpmError} onClose={() => setRpmError(null)} sx={{ mt: 1 }} />
{rpmError}
</Alert>
) : null} ) : null}
</> </>
) : rpmSelectedEntry && isRepoMetaFile(rpmSelectedEntry) ? ( ) : rpmSelectedEntry && isRepoMetaFile(rpmSelectedEntry) ? (
@@ -1171,11 +1165,7 @@ export default function RepoRpmDetailPage(props: RepoRpmDetailPageProps) {
Loading metadata file... Loading metadata file...
</Typography> </Typography>
) : null} ) : null}
{rpmMetaError ? ( <PageAlert severity="warning" message={rpmMetaError} onClose={() => setRpmMetaError(null)} sx={{ mt: 1 }} />
<Alert severity="warning" sx={{ mt: 1 }}>
{rpmMetaError}
</Alert>
) : null}
{!rpmMetaLoading && !rpmMetaError ? <LazyCodeBlock code={rpmMetaContent} language="xml" showLineNumbers /> : null} {!rpmMetaLoading && !rpmMetaError ? <LazyCodeBlock code={rpmMetaContent} language="xml" showLineNumbers /> : null}
</Box> </Box>
) : rpmSelectedEntry && rpmSelectedEntry.type !== 'dir' ? ( ) : rpmSelectedEntry && rpmSelectedEntry.type !== 'dir' ? (
@@ -1200,7 +1190,7 @@ export default function RepoRpmDetailPage(props: RepoRpmDetailPageProps) {
<Dialog open={subdirOpen} onClose={() => setSubdirOpen(false)} maxWidth="sm" fullWidth> <Dialog open={subdirOpen} onClose={() => setSubdirOpen(false)} maxWidth="sm" fullWidth>
<DialogTitle>New Subdirectory</DialogTitle> <DialogTitle>New Subdirectory</DialogTitle>
<FormDialogContent> <FormDialogContent>
{subdirError ? <Alert severity="error">{subdirError}</Alert> : null} <PageAlert message={subdirError} onClose={() => setSubdirError(null)} />
<Box sx={{ display: 'grid', gap: 1, mt: 1 }}> <Box sx={{ display: 'grid', gap: 1, mt: 1 }}>
<TextField <TextField
label="Name" label="Name"
@@ -1302,7 +1292,7 @@ export default function RepoRpmDetailPage(props: RepoRpmDetailPageProps) {
<Dialog open={uploadOpen} onClose={() => setUploadOpen(false)} maxWidth="sm" fullWidth> <Dialog open={uploadOpen} onClose={() => setUploadOpen(false)} maxWidth="sm" fullWidth>
<DialogTitle>Upload RPM</DialogTitle> <DialogTitle>Upload RPM</DialogTitle>
<FormDialogContent> <FormDialogContent>
{uploadError ? <Alert severity="error">{uploadError}</Alert> : null} <PageAlert message={uploadError} onClose={() => setUploadError(null)} />
<Box sx={{ display: 'grid', gap: 1, mt: 1 }}> <Box sx={{ display: 'grid', gap: 1, mt: 1 }}>
<TextField <TextField
type="file" type="file"
@@ -1362,7 +1352,7 @@ export default function RepoRpmDetailPage(props: RepoRpmDetailPageProps) {
> >
<DialogTitle>Repo directory status</DialogTitle> <DialogTitle>Repo directory status</DialogTitle>
<FormDialogContent> <FormDialogContent>
{statusError ? <Alert severity="error">{statusError}</Alert> : null} <PageAlert message={statusError} onClose={() => setStatusError(null)} />
<Box sx={{ display: 'grid', gap: 0.5, mt: 1 }}> <Box sx={{ display: 'grid', gap: 0.5, mt: 1 }}>
<Typography variant="body2">Directory: {statusName}</Typography> <Typography variant="body2">Directory: {statusName}</Typography>
<Typography variant="body2">Path: {statusPath || '.'}</Typography> <Typography variant="body2">Path: {statusPath || '.'}</Typography>
@@ -1471,7 +1461,7 @@ export default function RepoRpmDetailPage(props: RepoRpmDetailPageProps) {
> >
<DialogTitle>{renameIsRepoDir ? 'Edit repo directory' : 'Rename folder'}</DialogTitle> <DialogTitle>{renameIsRepoDir ? 'Edit repo directory' : 'Rename folder'}</DialogTitle>
<FormDialogContent> <FormDialogContent>
{renameError ? <Alert severity="error">{renameError}</Alert> : null} <PageAlert message={renameError} onClose={() => setRenameError(null)} />
<Box sx={{ display: 'grid', gap: 1, mt: 1 }}> <Box sx={{ display: 'grid', gap: 1, mt: 1 }}>
<Typography variant="body2"> <Typography variant="body2">
Current name: {renameName} Current name: {renameName}
+3 -3
View File
@@ -3,7 +3,6 @@ import DeleteIcon from '@mui/icons-material/Delete'
import EditIcon from '@mui/icons-material/Edit' import EditIcon from '@mui/icons-material/Edit'
import LinkOffIcon from '@mui/icons-material/LinkOff' import LinkOffIcon from '@mui/icons-material/LinkOff'
import LinkIcon from '@mui/icons-material/Link' import LinkIcon from '@mui/icons-material/Link'
import Alert from '@mui/material/Alert'
import ConfirmDeleteDialog from '../components/ConfirmDeleteDialog' import ConfirmDeleteDialog from '../components/ConfirmDeleteDialog'
import FormDialogContent from '../components/FormDialogContent' import FormDialogContent from '../components/FormDialogContent'
import { import {
@@ -24,6 +23,7 @@ import {
} from '@mui/material' } from '@mui/material'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { Link, useParams } from 'react-router-dom' import { Link, useParams } from 'react-router-dom'
import PageAlert from '../components/PageAlert'
import { api, AvailableRepo, Project, Repo, RepoStats } from '../api' import { api, AvailableRepo, Project, Repo, RepoStats } from '../api'
import Autocomplete from '../components/Autocomplete' import Autocomplete from '../components/Autocomplete'
import ProjectNavBar from '../components/ProjectNavBar' import ProjectNavBar from '../components/ProjectNavBar'
@@ -415,7 +415,7 @@ export default function ReposPage() {
</Box> </Box>
} }
> >
{reposError ? <Alert severity="error">{reposError}</Alert> : null} <PageAlert message={reposError} onClose={() => setReposError(null)} />
{reposLoading ? ( {reposLoading ? (
<Typography variant="body2" color="text.secondary" sx={{ mb: 1 }}> <Typography variant="body2" color="text.secondary" sx={{ mb: 1 }}>
Loading repositories... Loading repositories...
@@ -628,7 +628,7 @@ export default function ReposPage() {
<Dialog open={foreignOpen} onClose={closeForeign} maxWidth="sm" fullWidth> <Dialog open={foreignOpen} onClose={closeForeign} maxWidth="sm" fullWidth>
<DialogTitle>Add Foreign Repository</DialogTitle> <DialogTitle>Add Foreign Repository</DialogTitle>
<FormDialogContent> <FormDialogContent>
{foreignError ? <Alert severity="error">{foreignError}</Alert> : null} <PageAlert message={foreignError} onClose={() => setForeignError(null)} />
<TextField <TextField
margin="dense" margin="dense"
label="Search repositories" label="Search repositories"
+2 -2
View File
@@ -1,4 +1,3 @@
import Alert from '@mui/material/Alert'
import ContentCopyIcon from '@mui/icons-material/ContentCopy' import ContentCopyIcon from '@mui/icons-material/ContentCopy'
import { import {
Box, Box,
@@ -21,6 +20,7 @@ import SectionCard from '../components/SectionCard'
import SSHCertificateInspectDialog from '../components/SSHCertificateInspectDialog' import SSHCertificateInspectDialog from '../components/SSHCertificateInspectDialog'
import SSHCertificateIssuedDialog from '../components/SSHCertificateIssuedDialog' import SSHCertificateIssuedDialog from '../components/SSHCertificateIssuedDialog'
import SSHUserCADetailsDialog from '../components/SSHUserCADetailsDialog' import SSHUserCADetailsDialog from '../components/SSHUserCADetailsDialog'
import PageAlert from '../components/PageAlert'
import { api, SSHPrincipalGrant, SSHSignUserKeyResponse, SSHUserCA, SSHUserCAIssuance, SSHUserCASelfSignPayload } from '../api' import { api, SSHPrincipalGrant, SSHSignUserKeyResponse, SSHUserCA, SSHUserCAIssuance, SSHUserCASelfSignPayload } from '../api'
import SelectField from '../components/SelectField' import SelectField from '../components/SelectField'
@@ -316,7 +316,7 @@ export default function SSHCertificatesPage() {
return ( return (
<Box sx={{ display: 'grid', gap: 1 }}> <Box sx={{ display: 'grid', gap: 1 }}>
<Typography variant="h5">SSH Certificates</Typography> <Typography variant="h5">SSH Certificates</Typography>
{error ? <Alert severity="error">{error}</Alert> : null} <PageAlert message={error} onClose={() => setError(null)} />
<SectionCard <SectionCard
collapsible collapsible
collapseStorageKey="ssh-certificates:certificates" collapseStorageKey="ssh-certificates:certificates"
+3 -2
View File
@@ -20,6 +20,7 @@ import { ListRowActionButton, ListRowActions } from '../components/ListRowAction
import MonospaceTextField from '../components/MonospaceTextField' import MonospaceTextField from '../components/MonospaceTextField'
import SectionCard from '../components/SectionCard' import SectionCard from '../components/SectionCard'
import SSHCredentialDetailsDialog from '../components/SSHCredentialDetailsDialog' import SSHCredentialDetailsDialog from '../components/SSHCredentialDetailsDialog'
import PageAlert from '../components/PageAlert'
import { api, SSHCredential, SSHCredentialUpsertPayload } from '../api' import { api, SSHCredential, SSHCredentialUpsertPayload } from '../api'
import CompactListItemText from '../components/CompactListItemText' import CompactListItemText from '../components/CompactListItemText'
@@ -141,7 +142,7 @@ export default function SSHCredentialsPage() {
</HeaderActionButton> </HeaderActionButton>
} }
> >
{error ? <Alert severity="error" sx={{ mb: 1 }}>{error}</Alert> : null} <PageAlert message={error} onClose={() => setError(null)} sx={{ mb: 1 }} />
<List> <List>
{items.map((item) => ( {items.map((item) => (
<ListItem key={item.id} divider sx={{ alignItems: 'flex-start' }}> <ListItem key={item.id} divider sx={{ alignItems: 'flex-start' }}>
@@ -179,7 +180,7 @@ export default function SSHCredentialsPage() {
<Dialog open={dialogOpen} onClose={() => setDialogOpen(false)} maxWidth="md" fullWidth> <Dialog open={dialogOpen} onClose={() => setDialogOpen(false)} maxWidth="md" fullWidth>
<DialogTitle>{editingItem ? 'Edit SSH Credential' : 'Import SSH Credential'}</DialogTitle> <DialogTitle>{editingItem ? 'Edit SSH Credential' : 'Import SSH Credential'}</DialogTitle>
<FormDialogContent> <FormDialogContent>
{dialogError ? <Alert severity="error">{dialogError}</Alert> : null} <PageAlert message={dialogError} onClose={() => setDialogError(null)} />
<TextField label="Name" value={form.name} onChange={(event) => setForm((prev) => ({ ...prev, name: event.target.value }))} /> <TextField label="Name" value={form.name} onChange={(event) => setForm((prev) => ({ ...prev, name: event.target.value }))} />
<TextField label="Description" value={form.description} onChange={(event) => setForm((prev) => ({ ...prev, description: event.target.value }))} /> <TextField label="Description" value={form.description} onChange={(event) => setForm((prev) => ({ ...prev, description: event.target.value }))} />
{editingItem ? null : ( {editingItem ? null : (
+2 -2
View File
@@ -1,4 +1,3 @@
import Alert from '@mui/material/Alert'
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 Link from '@mui/material/Link' import Link from '@mui/material/Link'
@@ -24,6 +23,7 @@ import SSHServerFormDialog from '../components/SSHServerFormDialog'
import { SSHServerFormState } from '../components/SSHServerFormDialog' import { SSHServerFormState } from '../components/SSHServerFormDialog'
import SSHHostKeysDialog from '../components/SSHHostKeysDialog' import SSHHostKeysDialog from '../components/SSHHostKeysDialog'
import SSHUserCADetailsDialog from '../components/SSHUserCADetailsDialog' import SSHUserCADetailsDialog from '../components/SSHUserCADetailsDialog'
import PageAlert from '../components/PageAlert'
import { api, SSHAccessProfile, SSHAccessProfileSelfUpsertPayload, SSHConnectPayload, SSHCredential, SSHServer, SSHServerGroup, SSHServerHostKey, SSHServerUpsertPayload, SSHUserCA } from '../api' import { api, SSHAccessProfile, SSHAccessProfileSelfUpsertPayload, SSHConnectPayload, SSHCredential, SSHServer, SSHServerGroup, SSHServerHostKey, SSHServerUpsertPayload, SSHUserCA } from '../api'
const emptyForm = (): SSHAccessProfileFormState => ({ const emptyForm = (): SSHAccessProfileFormState => ({
@@ -807,7 +807,7 @@ export default function SSHServersPage() {
Session History Session History
</Button> </Button>
</Box> </Box>
{error ? <Alert severity="error">{error}</Alert> : null} <PageAlert message={error} onClose={() => setError(null)} />
<SectionCard <SectionCard
collapsible collapsible
collapseStorageKey="ssh-servers:shared-with-me" collapseStorageKey="ssh-servers:shared-with-me"
+6 -6
View File
@@ -1,5 +1,4 @@
import '@xterm/xterm/css/xterm.css' import '@xterm/xterm/css/xterm.css'
import Alert from '@mui/material/Alert'
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'
@@ -25,6 +24,7 @@ import SSHSessionFileDownloadDialog from '../components/SSHSessionFileDownloadDi
import SSHSessionFileUploadDialog from '../components/SSHSessionFileUploadDialog' import SSHSessionFileUploadDialog from '../components/SSHSessionFileUploadDialog'
import SSHTranscriptDialog from '../components/SSHTranscriptDialog' import SSHTranscriptDialog from '../components/SSHTranscriptDialog'
import TintedPanel from '../components/TintedPanel' import TintedPanel from '../components/TintedPanel'
import PageAlert from '../components/PageAlert'
import { api, SSHAccessProfile, SSHServer, SSHSession, SSHSessionConnectResponse, SSHSessionListResponse } from '../api' import { api, SSHAccessProfile, SSHServer, SSHSession, SSHSessionConnectResponse, SSHSessionListResponse } from '../api'
type SSHSessionStreamMessage = { type SSHSessionStreamMessage = {
@@ -624,7 +624,7 @@ function SessionTerminalPanel(props: SessionPanelProps) {
</Menu> </Menu>
</Box> </Box>
</Box> </Box>
{error ? <Alert severity="error">{error}</Alert> : null} <PageAlert message={error} onClose={() => setError(null)} />
<Box <Box
ref={terminalHostRef} ref={terminalHostRef}
sx={{ sx={{
@@ -1444,7 +1444,7 @@ export default function SSHSessionPage() {
{loadingHistory ? 'Refreshing...' : 'Refresh'} {loadingHistory ? 'Refreshing...' : 'Refresh'}
</Button> </Button>
</Box> </Box>
{historyError ? <Alert severity="error">{historyError}</Alert> : null} <PageAlert message={historyError} onClose={() => setHistoryError(null)} />
<Box sx={{ display: 'grid', gap: 0, minHeight: 0, flex: '1 1 auto', overflowY: 'auto' }}> <Box sx={{ display: 'grid', gap: 0, minHeight: 0, flex: '1 1 auto', overflowY: 'auto' }}>
{loadingHistory && !historyItems.length ? ( {loadingHistory && !historyItems.length ? (
<Typography variant="body2" color="text.secondary"> <Typography variant="body2" color="text.secondary">
@@ -1552,9 +1552,9 @@ export default function SSHSessionPage() {
</Box> </Box>
</Box> </Box>
<Box sx={{ display: 'grid', gap: 1, minHeight: 0 }}> <Box sx={{ display: 'grid', gap: 1, minHeight: 0 }}>
{pageError ? <Alert severity="error">{pageError}</Alert> : null} <PageAlert message={pageError} onClose={() => setPageError(null)} />
{connectFailures.length > 0 ? ( {connectFailures.length > 0 ? (
<Alert severity="warning" onClose={() => setConnectFailures([])}> <PageAlert severity="warning" onClose={() => setConnectFailures([])}>
<Typography variant="body2"> <Typography variant="body2">
{connectFailures.length} SSH connection{connectFailures.length === 1 ? '' : 's'} failed. {connectFailures.length} SSH connection{connectFailures.length === 1 ? '' : 's'} failed.
</Typography> </Typography>
@@ -1563,7 +1563,7 @@ export default function SSHSessionPage() {
{item} {item}
</Typography> </Typography>
))} ))}
</Alert> </PageAlert>
) : null} ) : null}
</Box> </Box>
{/*openSessionIDs.length ? ( {/*openSessionIDs.length ? (