Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
daca53c
Update Docker workflow to support new branch naming and improve image…
breyr Mar 2, 2025
0a66c41
Refactor Docker workflow to use correct pull request number syntax fo…
breyr Mar 2, 2025
3926313
Update action (#54)
breyr Mar 2, 2025
d7f5fb4
Red 76 UI changes (#55)
Tylermui Mar 3, 2025
2d2d9ab
refactored react code (#56)
breyr Mar 4, 2025
b4d6da1
Red 72 topology send create and delete link requests (#57)
breyr Mar 4, 2025
4220c89
Auth and onboarding fixes (#58)
breyr Mar 7, 2025
7f754ec
Bug fixes (#59)
breyr Mar 20, 2025
a2b4030
DONE (#60)
Tylermui Mar 20, 2025
3366342
table (#61)
Tylermui Mar 20, 2025
6a7f60e
Implement table structure and layout adjustments (#62)
breyr Mar 21, 2025
acf79af
Connections table again (#63)
breyr Mar 21, 2025
a27088e
Refactor table layout for improved responsiveness (#64)
breyr Mar 21, 2025
e0adcac
Stretch features (#65)
breyr Mar 21, 2025
f9fea0f
Stretch features (#66)
breyr Mar 22, 2025
acad8f8
Stretch features (#67)
breyr Mar 22, 2025
8241fb7
Stretch features (#68)
breyr Mar 24, 2025
d790a3c
Stretch features (#69)
breyr Mar 25, 2025
361993b
Stretch features (#70)
breyr Mar 25, 2025
3a0fe9e
Stretch features (#71)
breyr Mar 25, 2025
b3f11b8
Stretch features (#72)
breyr Mar 25, 2025
067b917
Stretch features (#73)
breyr Mar 28, 2025
dc97442
Stretch features (#74)
breyr Mar 28, 2025
181ff1d
Merge branch 'main' into dev
breyr Mar 31, 2025
fcdfdd5
Enhance port calculation logic to adjust for device number in VLAN ma…
breyr Mar 31, 2025
cd5c521
Bug fixes (#77)
breyr Mar 31, 2025
8482cc3
Refactor link operations and enhance substring extraction for port fo…
breyr Mar 31, 2025
117654d
Bump vite version (#79)
breyr Mar 31, 2025
2c18564
Topology card updates (#81)
breyr Mar 31, 2025
96090a0
Enhance context menu and link creation modal to display available por…
breyr Mar 31, 2025
7cf332a
More fixes (#83)
breyr Apr 1, 2025
8fce0e7
Update README.md (#85)
AdamSpera Apr 4, 2025
6b588db
Merge branch 'main' into dev
breyr Apr 8, 2025
3cb2de9
fix for links reinitializing if someone changes the topology name (#89)
breyr Apr 8, 2025
a3700c4
Handle device not found error in booking and unbooking operations; ad…
breyr Apr 21, 2025
485347e
Add device existence check in CreateLinkModal for filtering available…
breyr Apr 21, 2025
8c58e73
Merge branch 'main' into dev
breyr Apr 21, 2025
b75da3c
bump version
breyr Apr 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions backend/src/controllers/DeviceController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,9 +148,13 @@ export class DeviceController {
bookedDevice: device
});
}

res.status(200).json(device);
} catch (error) {
if (error instanceof Error && error.message === "ALREADY_BOOKED") {
if (error instanceof Error && error.message === "DEVICE_NOT_FOUND") {
// bypass as a success becuase it should just delete the topology
res.status(200);
} else if (error instanceof Error && error.message === "ALREADY_BOOKED") {
res.status(409).json({ error: "Device already booked" });
} else {
next(error);
Expand All @@ -172,7 +176,10 @@ export class DeviceController {
}
res.status(200).json(device);
} catch (error) {
if (error instanceof Error && error.message === "UNAUTHORIZED") {
if (error instanceof Error && error.message === "DEVICE_NOT_FOUND") {
// bypass as a success becuase it should just delete the topology
res.status(200);
} else if (error instanceof Error && error.message === "UNAUTHORIZED") {
res.status(401).json({ error: "You are not authorized to unbook this device." });
} else {
next(error);
Expand Down
10 changes: 9 additions & 1 deletion backend/src/repositories/PrismaDeviceRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,10 @@ export class PrismaDeviceRepository implements IDeviceRepository {
where: { id: deviceId },
});

if (current?.userId && current.userId !== userId) {
if (!current) {
// device not found
throw new Error("DEVICE_NOT_FOUND");
} else if (current?.userId && current.userId !== userId) {
throw new Error("ALREADY_BOOKED");
} else if (current?.userId === userId) {
// device is already booked by the same user, return current device
Expand All @@ -116,6 +119,11 @@ export class PrismaDeviceRepository implements IDeviceRepository {
select: { userId: true }
});

// device not found
if (!current) {
throw new Error("DEVICE_NOT_FOUND");
}

// only allow unbooking of device if userIds match AND account type is not admin or owner
if (accountType !== 'ADMIN' && accountType !== 'OWNER' && current?.userId !== userId) {
throw new Error("UNAUTHORIZED");
Expand Down
6 changes: 3 additions & 3 deletions compose.prod.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ services:
- postgres_data:/var/lib/postgresql/data

backend:
image: breyr/top-backend:1.0.3
image: breyr/top-backend:1.0.4
container_name: backend
environment:
DATABASE_URL: postgres://demo:demo@postgres:5432/demo
Expand All @@ -25,15 +25,15 @@ services:
- postgres

frontend:
image: breyr/top-frontend:1.0.3
image: breyr/top-frontend:1.0.4
container_name: frontend
ports:
- "80:80"
depends_on:
- backend

interconnect-api:
image: breyr/top-interconnectapi:1.0.3
image: breyr/top-interconnectapi:1.0.4
container_name: interconnect-api
environment:
SECRET_KEY: your_secret
Expand Down
12 changes: 9 additions & 3 deletions frontend/src/components/reactflow/overlayui/CreateLinkModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ interface CreateLinkModalProps {

export default function CreateLinkModal({ deviceData, currentDevicePorts, labDevices, onClose }: CreateLinkModalProps) {
const { user } = useAuth();
const { getEdges } = useReactFlow<Node<{ deviceData?: Device; }>, Edge>();
const { getEdges, getNodes } = useReactFlow<Node<{ deviceData?: Device; }>, Edge>();
const { createLink } = useLinkOperations();
const [selectedFirstDevice, setSelectedFirstDevice] = useState<string>(deviceData?.name ?? "");
const [selectedFirstDevicePort, setSelectedFirstDevicePort] = useState<string>("");
Expand Down Expand Up @@ -101,6 +101,12 @@ export default function CreateLinkModal({ deviceData, currentDevicePorts, labDev
}
}, [selectedSecondDevice, getEdges, labDevices]);

const deviceOnTopology = (deviceName: string) => {
const nodes = getNodes();
const hasDevice = nodes.filter(d => d.data.deviceData?.name === deviceName);
return hasDevice.length > 0; // Return true if device exists on topology
}

return (
<section className="bg-zinc-950 bg-opacity-50 w-full h-full fixed top-0 left-0 flex items-center justify-center z-50">
<div className="bg-[#ffffff] w-2/5 p-6 rounded-lg shadow-lg">
Expand All @@ -121,7 +127,7 @@ export default function CreateLinkModal({ deviceData, currentDevicePorts, labDev
disabled={!!deviceData?.name}
>
<option value="">Select a Device</option>
{labDevices.filter((device) => device.userId == null || device.userId == user?.id).map((device) => {
{labDevices.filter((device) => (device.userId == null || device.userId == user?.id) && deviceOnTopology(device.name)).map((device) => {
const portsArray = device.ports.split(',');
const generatedPorts = portsArray.flatMap(portDef => generatePorts(portDef));
const hasAvailablePorts = generatedPorts.some(port => !firstDeviceOccupiedPorts.includes(port));
Expand Down Expand Up @@ -157,7 +163,7 @@ export default function CreateLinkModal({ deviceData, currentDevicePorts, labDev
className="block w-full mt-1 rounded-md bg-[#ffffff] focus:outline-none"
>
<option value="">Select a Device</option>
{labDevices.filter((d) => d.name !== deviceData?.name).map((device) => {
{labDevices.filter((d) => d.name !== deviceData?.name && deviceOnTopology(d.name)).map((device) => {
const portsArray = device.ports.split(',');
const generatedPorts = portsArray.flatMap(portDef => generatePorts(portDef));
const occupiedPorts = secondDeviceOccupiedPorts[device.name] || [];
Expand Down
24 changes: 23 additions & 1 deletion frontend/src/hooks/useLinkOperations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export function useLinkOperationsBase() {
const fetchConnectionDetails = async (deviceName: string, devicePort: string) => {
const conns = await authenticatedApiClient.getConnectionsByDeviceName(deviceName);
return conns.data?.find(c => c.labDevicePort === devicePort);
}
};

// get interconnect information
const fetchInterconnectDevice = async (connectionInfo: Connection) => {
Expand Down Expand Up @@ -69,6 +69,21 @@ export function useLinkOperationsBase() {
return devicePort;
};

const checkDevicesExist = async (firstDeviceName: string, secondDeviceName: string): Promise<boolean> => {
try {
const devices = await authenticatedApiClient.getAllDevices();
const firstDeviceMatches = devices.data?.filter(d => d.name === firstDeviceName) || [];
const secondDeviceMatches = devices.data?.filter(d => d.name === secondDeviceName) || [];

// Return true if one or both devices don't exist
return firstDeviceMatches.length === 0 || secondDeviceMatches.length === 0;
} catch (error) {
// In case of API error, assume devices don't exist for safety
console.error("Error checking device existence:", error);
return true;
}
};

// API operations without ReactFlow dependencies
const performLinkOperation = async (params: LinkOperationParams, operation: 'create' | 'delete', createToastPerLink: boolean = true) => {
const { firstDeviceName, firstDevicePort, secondDeviceName, secondDevicePort } = params;
Expand Down Expand Up @@ -103,6 +118,13 @@ export function useLinkOperationsBase() {
return false;
}

// Check to see if the devices exist, if one or both don't anymore
// just show the Toast as a success and do not perform the interconnect configs
const devicesNotFound = await checkDevicesExist(firstDeviceName, secondDeviceName);
if (devicesNotFound) {
return true; // return true to indicate success
}

// Prepare link payload
// Get the correct interconnect information based on the device number for the interconnect
const [interconnect1, interconnect2] = firstInterconnectInfo?.deviceNumber === 1
Expand Down