Updates
This commit is contained in:
@@ -25,6 +25,7 @@ services:
|
|||||||
git \
|
git \
|
||||||
ca-certificates
|
ca-certificates
|
||||||
rm -rf /var/lib/apt/lists/*
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
git config set advice.defaultBranchName false
|
||||||
if [ -f /apps/package.json ] && [ -d /apps/.git ]; then
|
if [ -f /apps/package.json ] && [ -d /apps/.git ]; then
|
||||||
cd /apps
|
cd /apps
|
||||||
git reset --hard
|
git reset --hard
|
||||||
@@ -38,9 +39,10 @@ services:
|
|||||||
git fetch origin main
|
git fetch origin main
|
||||||
git checkout -B main origin/main
|
git checkout -B main origin/main
|
||||||
fi
|
fi
|
||||||
npm install
|
npm install --include=dev
|
||||||
npm run build
|
npm run build
|
||||||
npm run start
|
npm prune --omit=dev
|
||||||
|
node server/index.js
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
- dcm_data:/apps/data
|
- dcm_data:/apps/data
|
||||||
@@ -54,4 +56,4 @@ volumes:
|
|||||||
networks:
|
networks:
|
||||||
dcm_internal:
|
dcm_internal:
|
||||||
driver: overlay
|
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 rackHeight = rack.sizeU * RACK_ROW_HEIGHT;
|
||||||
const horizontalEquipment = rack.equipment.filter((item) => !(item.type === 'pdu' && item.pduMount === 'vertical'));
|
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 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 (
|
return (
|
||||||
<Paper
|
<Box sx={{ width: 270, flex: '0 0 270px', display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||||
variant="outlined"
|
<Paper
|
||||||
onClick={onSelectRack}
|
variant="outlined"
|
||||||
sx={{
|
onClick={onSelectRack}
|
||||||
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
|
|
||||||
sx={{
|
sx={{
|
||||||
p: 1.25,
|
width: '100%',
|
||||||
bgcolor: (theme) => (theme.palette.mode === 'dark' ? 'rgba(9, 20, 20, 0.78)' : 'rgba(218, 232, 220, 0.96)'),
|
overflow: 'hidden',
|
||||||
borderBottom: 1,
|
borderColor: (theme) =>
|
||||||
borderColor: 'divider',
|
selected?.kind === 'rack' && selected.rackId === rack.id
|
||||||
backgroundImage: (theme) =>
|
? 'primary.main'
|
||||||
theme.palette.mode === 'dark'
|
: theme.palette.mode === 'dark'
|
||||||
? 'linear-gradient(90deg, rgba(97, 255, 207, 0.12), rgba(240, 184, 77, 0.04))'
|
? 'rgba(97, 255, 207, 0.16)'
|
||||||
: 'linear-gradient(90deg, rgba(0, 108, 95, 0.12), rgba(156, 92, 12, 0.07))',
|
: '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
|
||||||
<Box sx={{ minWidth: 0 }}>
|
sx={{
|
||||||
<Typography variant="subtitle2" noWrap sx={{ fontWeight: 800 }}>
|
p: 1.25,
|
||||||
{rack.name}
|
bgcolor: (theme) => (theme.palette.mode === 'dark' ? 'rgba(9, 20, 20, 0.78)' : 'rgba(218, 232, 220, 0.96)'),
|
||||||
</Typography>
|
borderBottom: 1,
|
||||||
<Typography variant="caption" color="text.secondary">
|
borderColor: 'divider',
|
||||||
{rack.sizeU}U {rack.type}
|
backgroundImage: (theme) =>
|
||||||
</Typography>
|
theme.palette.mode === 'dark'
|
||||||
</Box>
|
? 'linear-gradient(90deg, rgba(97, 255, 207, 0.12), rgba(240, 184, 77, 0.04))'
|
||||||
<Chip size="small" label={`${occupancy.percent}%`} color={occupancy.percent > 85 ? 'warning' : 'default'} />
|
: 'linear-gradient(90deg, rgba(0, 108, 95, 0.12), rgba(156, 92, 12, 0.07))',
|
||||||
</Stack>
|
}}
|
||||||
<LinearProgress variant="determinate" value={occupancy.percent} sx={{ mt: 1, height: 5, borderRadius: 1 }} />
|
>
|
||||||
</Box>
|
<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
|
<Box
|
||||||
onDrop={onDrop}
|
onDrop={onDrop}
|
||||||
@@ -1515,6 +1517,50 @@ function RackView({
|
|||||||
})}
|
})}
|
||||||
</Box>
|
</Box>
|
||||||
</Paper>
|
</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="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="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 />
|
<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}>
|
<Button color="error" variant="outlined" startIcon={<Delete />} onClick={onDelete}>
|
||||||
Delete rack
|
Delete rack
|
||||||
|
|||||||
@@ -93,6 +93,9 @@ export interface RackContainer {
|
|||||||
manufacturer: string;
|
manufacturer: string;
|
||||||
model: string;
|
model: string;
|
||||||
location: string;
|
location: string;
|
||||||
|
locationArea: string;
|
||||||
|
locationRow: string;
|
||||||
|
locationColumn: string;
|
||||||
notes: string;
|
notes: string;
|
||||||
equipment: Equipment[];
|
equipment: Equipment[];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,9 @@ export const createRack = (type: 'rack' | 'cabinet', index: number, sizeU = 42):
|
|||||||
manufacturer: '',
|
manufacturer: '',
|
||||||
model: '',
|
model: '',
|
||||||
location: '',
|
location: '',
|
||||||
|
locationArea: '',
|
||||||
|
locationRow: '',
|
||||||
|
locationColumn: '',
|
||||||
notes: '',
|
notes: '',
|
||||||
equipment: [],
|
equipment: [],
|
||||||
});
|
});
|
||||||
@@ -157,6 +160,9 @@ export const normalizeDiagram = (value: unknown): DiagramData => {
|
|||||||
manufacturer: rack.manufacturer || '',
|
manufacturer: rack.manufacturer || '',
|
||||||
model: rack.model || '',
|
model: rack.model || '',
|
||||||
location: rack.location || '',
|
location: rack.location || '',
|
||||||
|
locationArea: rack.locationArea || rack.location || '',
|
||||||
|
locationRow: rack.locationRow || '',
|
||||||
|
locationColumn: rack.locationColumn || '',
|
||||||
notes: rack.notes || '',
|
notes: rack.notes || '',
|
||||||
equipment: Array.isArray(rack.equipment)
|
equipment: Array.isArray(rack.equipment)
|
||||||
? rack.equipment.map((item, itemIndex) => {
|
? rack.equipment.map((item, itemIndex) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user