Updates
This commit is contained in:
@@ -25,6 +25,7 @@ services:
|
||||
git \
|
||||
ca-certificates
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
git config set advice.defaultBranchName false
|
||||
if [ -f /apps/package.json ] && [ -d /apps/.git ]; then
|
||||
cd /apps
|
||||
git reset --hard
|
||||
@@ -38,9 +39,10 @@ services:
|
||||
git fetch origin main
|
||||
git checkout -B main origin/main
|
||||
fi
|
||||
npm install
|
||||
npm install --include=dev
|
||||
npm run build
|
||||
npm run start
|
||||
npm prune --omit=dev
|
||||
node server/index.js
|
||||
|
||||
volumes:
|
||||
- dcm_data:/apps/data
|
||||
@@ -54,4 +56,4 @@ volumes:
|
||||
networks:
|
||||
dcm_internal:
|
||||
driver: overlay
|
||||
attachable: true
|
||||
attachable: true
|
||||
|
||||
178
src/App.tsx
178
src/App.tsx
@@ -1314,59 +1314,61 @@ function RackView({
|
||||
const rackHeight = rack.sizeU * RACK_ROW_HEIGHT;
|
||||
const horizontalEquipment = rack.equipment.filter((item) => !(item.type === 'pdu' && item.pduMount === 'vertical'));
|
||||
const verticalPdus = rack.equipment.filter((item) => item.type === 'pdu' && item.pduMount === 'vertical');
|
||||
const totalWatts = rack.equipment.reduce((sum, item) => sum + item.powerWatts, 0);
|
||||
const locationDisplay = [rack.locationArea, rack.locationRow, rack.locationColumn].filter(Boolean).join(' / ') || 'Unassigned';
|
||||
|
||||
return (
|
||||
<Paper
|
||||
variant="outlined"
|
||||
onClick={onSelectRack}
|
||||
sx={{
|
||||
width: 270,
|
||||
flex: '0 0 270px',
|
||||
overflow: 'hidden',
|
||||
borderColor: (theme) =>
|
||||
selected?.kind === 'rack' && selected.rackId === rack.id
|
||||
? 'primary.main'
|
||||
: theme.palette.mode === 'dark'
|
||||
? 'rgba(97, 255, 207, 0.16)'
|
||||
: 'rgba(0, 108, 95, 0.24)',
|
||||
bgcolor: (theme) => (theme.palette.mode === 'dark' ? 'rgba(3, 8, 9, 0.72)' : 'rgba(238, 244, 236, 0.96)'),
|
||||
borderRadius: 1,
|
||||
boxShadow: (theme) =>
|
||||
selected?.kind === 'rack' && selected.rackId === rack.id
|
||||
? theme.palette.mode === 'dark'
|
||||
? '0 0 34px rgba(97, 255, 207, 0.2), 0 18px 40px rgba(0, 0, 0, 0.32)'
|
||||
: '0 0 0 1px rgba(0, 108, 95, 0.18), 0 16px 34px rgba(23, 38, 33, 0.18)'
|
||||
: theme.palette.mode === 'dark'
|
||||
? '0 18px 40px rgba(0, 0, 0, 0.24)'
|
||||
: '0 14px 28px rgba(23, 38, 33, 0.14)',
|
||||
clipPath: 'polygon(0 0, 96% 0, 100% 12px, 100% 100%, 4% 100%, 0 calc(100% - 12px))',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
<Box sx={{ width: 270, flex: '0 0 270px', display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
<Paper
|
||||
variant="outlined"
|
||||
onClick={onSelectRack}
|
||||
sx={{
|
||||
p: 1.25,
|
||||
bgcolor: (theme) => (theme.palette.mode === 'dark' ? 'rgba(9, 20, 20, 0.78)' : 'rgba(218, 232, 220, 0.96)'),
|
||||
borderBottom: 1,
|
||||
borderColor: 'divider',
|
||||
backgroundImage: (theme) =>
|
||||
theme.palette.mode === 'dark'
|
||||
? 'linear-gradient(90deg, rgba(97, 255, 207, 0.12), rgba(240, 184, 77, 0.04))'
|
||||
: 'linear-gradient(90deg, rgba(0, 108, 95, 0.12), rgba(156, 92, 12, 0.07))',
|
||||
width: '100%',
|
||||
overflow: 'hidden',
|
||||
borderColor: (theme) =>
|
||||
selected?.kind === 'rack' && selected.rackId === rack.id
|
||||
? 'primary.main'
|
||||
: theme.palette.mode === 'dark'
|
||||
? 'rgba(97, 255, 207, 0.16)'
|
||||
: 'rgba(0, 108, 95, 0.24)',
|
||||
bgcolor: (theme) => (theme.palette.mode === 'dark' ? 'rgba(3, 8, 9, 0.72)' : 'rgba(238, 244, 236, 0.96)'),
|
||||
borderRadius: 1,
|
||||
boxShadow: (theme) =>
|
||||
selected?.kind === 'rack' && selected.rackId === rack.id
|
||||
? theme.palette.mode === 'dark'
|
||||
? '0 0 34px rgba(97, 255, 207, 0.2), 0 18px 40px rgba(0, 0, 0, 0.32)'
|
||||
: '0 0 0 1px rgba(0, 108, 95, 0.18), 0 16px 34px rgba(23, 38, 33, 0.18)'
|
||||
: theme.palette.mode === 'dark'
|
||||
? '0 18px 40px rgba(0, 0, 0, 0.24)'
|
||||
: '0 14px 28px rgba(23, 38, 33, 0.14)',
|
||||
clipPath: 'polygon(0 0, 96% 0, 100% 12px, 100% 100%, 4% 100%, 0 calc(100% - 12px))',
|
||||
}}
|
||||
>
|
||||
<Stack direction="row" justifyContent="space-between" alignItems="center" spacing={1}>
|
||||
<Box sx={{ minWidth: 0 }}>
|
||||
<Typography variant="subtitle2" noWrap sx={{ fontWeight: 800 }}>
|
||||
{rack.name}
|
||||
</Typography>
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
{rack.sizeU}U {rack.type}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Chip size="small" label={`${occupancy.percent}%`} color={occupancy.percent > 85 ? 'warning' : 'default'} />
|
||||
</Stack>
|
||||
<LinearProgress variant="determinate" value={occupancy.percent} sx={{ mt: 1, height: 5, borderRadius: 1 }} />
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
p: 1.25,
|
||||
bgcolor: (theme) => (theme.palette.mode === 'dark' ? 'rgba(9, 20, 20, 0.78)' : 'rgba(218, 232, 220, 0.96)'),
|
||||
borderBottom: 1,
|
||||
borderColor: 'divider',
|
||||
backgroundImage: (theme) =>
|
||||
theme.palette.mode === 'dark'
|
||||
? 'linear-gradient(90deg, rgba(97, 255, 207, 0.12), rgba(240, 184, 77, 0.04))'
|
||||
: 'linear-gradient(90deg, rgba(0, 108, 95, 0.12), rgba(156, 92, 12, 0.07))',
|
||||
}}
|
||||
>
|
||||
<Stack direction="row" justifyContent="space-between" alignItems="center" spacing={1}>
|
||||
<Box sx={{ minWidth: 0 }}>
|
||||
<Typography variant="subtitle2" noWrap sx={{ fontWeight: 800 }}>
|
||||
{rack.name}
|
||||
</Typography>
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
{rack.sizeU}U {rack.type}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Chip size="small" label={`${occupancy.percent}%`} color={occupancy.percent > 85 ? 'warning' : 'default'} />
|
||||
</Stack>
|
||||
<LinearProgress variant="determinate" value={occupancy.percent} sx={{ mt: 1, height: 5, borderRadius: 1 }} />
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
onDrop={onDrop}
|
||||
@@ -1515,6 +1517,50 @@ function RackView({
|
||||
})}
|
||||
</Box>
|
||||
</Paper>
|
||||
<Paper
|
||||
variant="outlined"
|
||||
onClick={onSelectRack}
|
||||
sx={{
|
||||
width: '100%',
|
||||
p: 1,
|
||||
cursor: 'pointer',
|
||||
borderColor: (theme) =>
|
||||
selected?.kind === 'rack' && selected.rackId === rack.id
|
||||
? 'primary.main'
|
||||
: theme.palette.mode === 'dark'
|
||||
? 'rgba(97, 255, 207, 0.18)'
|
||||
: 'rgba(0, 108, 95, 0.24)',
|
||||
bgcolor: (theme) => (theme.palette.mode === 'dark' ? 'rgba(7, 17, 16, 0.72)' : 'rgba(244, 249, 241, 0.9)'),
|
||||
backgroundImage: (theme) =>
|
||||
theme.palette.mode === 'dark'
|
||||
? 'linear-gradient(135deg, rgba(97, 255, 207, 0.08), rgba(240, 184, 77, 0.04))'
|
||||
: 'linear-gradient(135deg, rgba(0, 108, 95, 0.08), rgba(156, 92, 12, 0.05))',
|
||||
boxShadow: (theme) =>
|
||||
theme.palette.mode === 'dark'
|
||||
? '0 12px 28px rgba(0, 0, 0, 0.24), inset 0 1px 0 rgba(255, 255, 255, 0.05)'
|
||||
: '0 10px 22px rgba(23, 38, 33, 0.12), inset 0 1px 0 rgba(255, 255, 255, 0.65)',
|
||||
}}
|
||||
>
|
||||
<Stack spacing={0.45}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', gap: 1, minWidth: 0 }}>
|
||||
<Typography variant="caption" color="text.secondary" sx={{ flex: '0 0 auto', fontSize: 9, textTransform: 'uppercase' }}>
|
||||
Location
|
||||
</Typography>
|
||||
<Typography variant="caption" noWrap sx={{ minWidth: 0, fontWeight: 900, textAlign: 'right' }}>
|
||||
{locationDisplay}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', gap: 1, minWidth: 0 }}>
|
||||
<Typography variant="caption" color="text.secondary" sx={{ fontSize: 9, textTransform: 'uppercase' }}>
|
||||
Total Watts
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ fontWeight: 900 }}>
|
||||
{totalWatts.toLocaleString()} W
|
||||
</Typography>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1654,7 +1700,41 @@ function PropertiesPanel({
|
||||
/>
|
||||
<TextField label="Manufacturer" value={selectedRack.manufacturer} onChange={(event) => onRackChange(selectedRack.id, { manufacturer: event.target.value })} fullWidth />
|
||||
<TextField label="Model" value={selectedRack.model} onChange={(event) => onRackChange(selectedRack.id, { model: event.target.value })} fullWidth />
|
||||
<TextField label="Location" value={selectedRack.location} onChange={(event) => onRackChange(selectedRack.id, { location: event.target.value })} fullWidth />
|
||||
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={1}>
|
||||
<TextField
|
||||
label="Area"
|
||||
value={selectedRack.locationArea}
|
||||
onChange={(event) =>
|
||||
onRackChange(selectedRack.id, {
|
||||
locationArea: event.target.value,
|
||||
location: [event.target.value, selectedRack.locationRow, selectedRack.locationColumn].filter(Boolean).join('/'),
|
||||
})
|
||||
}
|
||||
fullWidth
|
||||
/>
|
||||
<TextField
|
||||
label="Row"
|
||||
value={selectedRack.locationRow}
|
||||
onChange={(event) =>
|
||||
onRackChange(selectedRack.id, {
|
||||
locationRow: event.target.value,
|
||||
location: [selectedRack.locationArea, event.target.value, selectedRack.locationColumn].filter(Boolean).join('/'),
|
||||
})
|
||||
}
|
||||
fullWidth
|
||||
/>
|
||||
<TextField
|
||||
label="Column"
|
||||
value={selectedRack.locationColumn}
|
||||
onChange={(event) =>
|
||||
onRackChange(selectedRack.id, {
|
||||
locationColumn: event.target.value,
|
||||
location: [selectedRack.locationArea, selectedRack.locationRow, event.target.value].filter(Boolean).join('/'),
|
||||
})
|
||||
}
|
||||
fullWidth
|
||||
/>
|
||||
</Stack>
|
||||
<TextField label="Notes" value={selectedRack.notes} onChange={(event) => onRackChange(selectedRack.id, { notes: event.target.value })} multiline minRows={3} fullWidth />
|
||||
<Button color="error" variant="outlined" startIcon={<Delete />} onClick={onDelete}>
|
||||
Delete rack
|
||||
|
||||
@@ -93,6 +93,9 @@ export interface RackContainer {
|
||||
manufacturer: string;
|
||||
model: string;
|
||||
location: string;
|
||||
locationArea: string;
|
||||
locationRow: string;
|
||||
locationColumn: string;
|
||||
notes: string;
|
||||
equipment: Equipment[];
|
||||
}
|
||||
|
||||
@@ -27,6 +27,9 @@ export const createRack = (type: 'rack' | 'cabinet', index: number, sizeU = 42):
|
||||
manufacturer: '',
|
||||
model: '',
|
||||
location: '',
|
||||
locationArea: '',
|
||||
locationRow: '',
|
||||
locationColumn: '',
|
||||
notes: '',
|
||||
equipment: [],
|
||||
});
|
||||
@@ -157,6 +160,9 @@ export const normalizeDiagram = (value: unknown): DiagramData => {
|
||||
manufacturer: rack.manufacturer || '',
|
||||
model: rack.model || '',
|
||||
location: rack.location || '',
|
||||
locationArea: rack.locationArea || rack.location || '',
|
||||
locationRow: rack.locationRow || '',
|
||||
locationColumn: rack.locationColumn || '',
|
||||
notes: rack.notes || '',
|
||||
equipment: Array.isArray(rack.equipment)
|
||||
? rack.equipment.map((item, itemIndex) => {
|
||||
|
||||
Reference in New Issue
Block a user