@@ -26,7 +26,7 @@ import {
2626 Tooltip ,
2727 Typography ,
2828} from "@mui/material" ;
29- import { useState } from "react" ;
29+ import { Fragment , useState } from "react" ;
3030import BackupIcon from "@mui/icons-material/Backup" ;
3131import RestoreIcon from "@mui/icons-material/SettingsBackupRestore" ;
3232import DeleteIcon from "@mui/icons-material/Delete" ;
@@ -56,6 +56,7 @@ import {
5656 type BackupConfigStatus ,
5757 type BackupConfigView ,
5858 type BackupMaintenanceRun ,
59+ groupServersByRank ,
5960 type RecentRun ,
6061 type ServerInfo ,
6162} from "../types" ;
@@ -1531,105 +1532,124 @@ function ServersPanel({
15311532 </ TableRow >
15321533 </ TableHead >
15331534 < TableBody >
1534- { members . map ( ( m ) => {
1535- const types = typesForServer ( m . id ) ;
1536- if ( types . length === 0 ) {
1537- return (
1538- < TableRow key = { m . id } >
1539- < TableCell >
1540- < Stack spacing = { 0.5 } sx = { { alignItems : "flex-start" } } >
1541- { serverLink ( m ) }
1542- { restoreControl ( m . id ) }
1543- </ Stack >
1544- </ TableCell >
1545- < TableCell colSpan = { 3 } >
1546- < Typography variant = "body2" color = "text.secondary" >
1547- No backup types registered yet
1535+ { groupServersByRank ( members ) . map ( ( [ rank , rankMembers ] ) => (
1536+ < Fragment key = { rank ?? "_unranked" } >
1537+ { rank && (
1538+ < TableRow >
1539+ < TableCell
1540+ colSpan = { 5 }
1541+ sx = { { borderBottom : "none" , pb : 0 } }
1542+ >
1543+ < Typography
1544+ variant = "overline"
1545+ color = "text.secondary"
1546+ >
1547+ { rank }
15481548 </ Typography >
15491549 </ TableCell >
1550- < TableCell align = "right" >
1551- < Tooltip title = "This server hasn't registered any backup types yet." >
1552- { /* span so the tooltip works on the disabled button */ }
1553- < span >
1554- < Button
1555- size = "small"
1556- variant = "outlined"
1557- startIcon = { < BackupIcon /> }
1558- disabled
1559- >
1560- Backup now
1561- </ Button >
1562- </ span >
1563- </ Tooltip >
1564- </ TableCell >
15651550 </ TableRow >
1566- ) ;
1567- }
1568- return types . map ( ( t , i ) => {
1569- const cap = capFor ( m . id , t ) ;
1570- return (
1571- < TableRow key = { `${ m . id } :${ t } ` } >
1572- { i === 0 && (
1573- < TableCell
1574- rowSpan = { types . length }
1575- sx = { { verticalAlign : "top" } }
1576- >
1577- < Stack
1578- spacing = { 0.5 }
1579- sx = { { alignItems : "flex-start" } }
1580- >
1581- { serverLink ( m ) }
1582- { restoreControl ( m . id ) }
1583- </ Stack >
1584- </ TableCell >
1585- ) }
1586- < TableCell >
1587- < Stack
1588- direction = "row"
1589- spacing = { 1 }
1590- sx = { { alignItems : "center" } }
1591- >
1592- < Typography
1593- variant = "body2"
1594- sx = { { fontFamily : "monospace" } }
1595- >
1596- { t }
1597- </ Typography >
1598- { cap ?. enabled === false && (
1599- < Tooltip title = "Not on the backup schedule for this server (toggle it on in the server's Backups section). You can still back it up on demand." >
1600- < Chip
1601- size = "small"
1602- variant = "outlined"
1603- label = "not scheduled"
1604- />
1551+ ) }
1552+ { rankMembers . map ( ( m ) => {
1553+ const types = typesForServer ( m . id ) ;
1554+ if ( types . length === 0 ) {
1555+ return (
1556+ < TableRow key = { m . id } >
1557+ < TableCell >
1558+ < Stack spacing = { 0.5 } sx = { { alignItems : "flex-start" } } >
1559+ { serverLink ( m ) }
1560+ { restoreControl ( m . id ) }
1561+ </ Stack >
1562+ </ TableCell >
1563+ < TableCell colSpan = { 3 } >
1564+ < Typography variant = "body2" color = "text.secondary" >
1565+ No backup types registered yet
1566+ </ Typography >
1567+ </ TableCell >
1568+ < TableCell align = "right" >
1569+ < Tooltip title = "This server hasn't registered any backup types yet." >
1570+ { /* span so the tooltip works on the disabled button */ }
1571+ < span >
1572+ < Button
1573+ size = "small"
1574+ variant = "outlined"
1575+ startIcon = { < BackupIcon /> }
1576+ disabled
1577+ >
1578+ Backup now
1579+ </ Button >
1580+ </ span >
16051581 </ Tooltip >
1582+ </ TableCell >
1583+ </ TableRow >
1584+ ) ;
1585+ }
1586+ return types . map ( ( t , i ) => {
1587+ const cap = capFor ( m . id , t ) ;
1588+ return (
1589+ < TableRow key = { `${ m . id } :${ t } ` } >
1590+ { i === 0 && (
1591+ < TableCell
1592+ rowSpan = { types . length }
1593+ sx = { { verticalAlign : "top" } }
1594+ >
1595+ < Stack
1596+ spacing = { 0.5 }
1597+ sx = { { alignItems : "flex-start" } }
1598+ >
1599+ { serverLink ( m ) }
1600+ { restoreControl ( m . id ) }
1601+ </ Stack >
1602+ </ TableCell >
16061603 ) }
1607- < BackupProcessingChip
1608- since = { cap ?. processing_since }
1609- />
1610- </ Stack >
1611- </ TableCell >
1612- < TableCell >
1613- { cap ?. next_backup_at ? (
1614- < TimeAgo timestamp = { cap . next_backup_at } />
1615- ) : (
1616- < Typography variant = "body2" color = "text.secondary" >
1617- —
1618- </ Typography >
1619- ) }
1620- </ TableCell >
1621- < TableCell >
1622- < LatestSnapshot
1623- id = { cap ?. latest_snapshot_id }
1624- at = { cap ?. latest_snapshot_at }
1625- bytes = { cap ?. latest_snapshot_bytes }
1626- />
1627- </ TableCell >
1628- < TableCell align = "right" > { actionCell ( m . id , t ) } </ TableCell >
1629- </ TableRow >
1630- ) ;
1631- } ) ;
1632- } ) }
1604+ < TableCell >
1605+ < Stack
1606+ direction = "row"
1607+ spacing = { 1 }
1608+ sx = { { alignItems : "center" } }
1609+ >
1610+ < Typography
1611+ variant = "body2"
1612+ sx = { { fontFamily : "monospace" } }
1613+ >
1614+ { t }
1615+ </ Typography >
1616+ { cap ?. enabled === false && (
1617+ < Tooltip title = "Not on the backup schedule for this server (toggle it on in the server's Backups section). You can still back it up on demand." >
1618+ < Chip
1619+ size = "small"
1620+ variant = "outlined"
1621+ label = "not scheduled"
1622+ />
1623+ </ Tooltip >
1624+ ) }
1625+ < BackupProcessingChip
1626+ since = { cap ?. processing_since }
1627+ />
1628+ </ Stack >
1629+ </ TableCell >
1630+ < TableCell >
1631+ { cap ?. next_backup_at ? (
1632+ < TimeAgo timestamp = { cap . next_backup_at } />
1633+ ) : (
1634+ < Typography variant = "body2" color = "text.secondary" >
1635+ —
1636+ </ Typography >
1637+ ) }
1638+ </ TableCell >
1639+ < TableCell >
1640+ < LatestSnapshot
1641+ id = { cap ?. latest_snapshot_id }
1642+ at = { cap ?. latest_snapshot_at }
1643+ bytes = { cap ?. latest_snapshot_bytes }
1644+ />
1645+ </ TableCell >
1646+ < TableCell align = "right" > { actionCell ( m . id , t ) } </ TableCell >
1647+ </ TableRow >
1648+ ) ;
1649+ } ) ;
1650+ } ) }
1651+ </ Fragment >
1652+ ) ) }
16331653 </ TableBody >
16341654 </ Table >
16351655 </ Box >
0 commit comments