Compare commits
2 Commits
206b2e5c51
...
ad776f31fb
| Author | SHA1 | Date | |
|---|---|---|---|
| ad776f31fb | |||
| 0c173d2734 |
@@ -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) => (
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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...
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 ? (
|
||||||
|
|||||||
@@ -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...
|
||||||
|
|||||||
@@ -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' }}>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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}`} />
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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)} />
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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...
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,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"
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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 : (
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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 ? (
|
||||||
|
|||||||
Reference in New Issue
Block a user