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