Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(service): only discover devices and add the devices if there are no devices in data store #62

Merged
merged 13 commits into from
Dec 10, 2024
Merged
Changes from 1 commit
Commits
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
Prev Previous commit
Next Next commit
feat(webui): device discovery in cfm webui (#58)
* feat: set the nodes width based on the device id dimensions

* feat: add ipaddress to the existed devices in dashboard

* feat: add new button to discover new devices and the related interfaces

* fix: fix the bug in addDiscoveredHosts interface

* fix: fix bug in addDiscoveredBlades interface

* feat: add function addDiscoveredDevices with output popup and waiting popup

* style:  separate lines for discovered blades and hosts

* feat: add waiting progress for device discovery

* feat: update the content after adding new discovered devices

* feat: distinguish node status by border color

* feat: prevent the user from manually adding blades to the CMA_Discovered_Blades appliance

* style: remove unnecessary stype setup
Meng-20 authored Dec 6, 2024
commit 5c63d28aa552b2ae9f0513a8627a155ea1459599
14 changes: 12 additions & 2 deletions webui/src/components/Appliance/Appliances.vue
Original file line number Diff line number Diff line change
@@ -93,6 +93,7 @@
variant="text"
id="addBlade"
@click="addNewBladeWindowButton"
:disabled="isAddBladeButtonDisabled"
>
<v-icon start color="#6ebe4a">mdi-plus-thick</v-icon>
BLADE
@@ -1524,6 +1525,12 @@ export default {
ComposeMemoryButton,
},

computed: {
isAddBladeButtonDisabled() {
return this.selectedApplianceId === "CMA_Discovered_Blades";
},
},

methods: {
/* Open the add appliance popup */
addNewApplianceWindowButton() {
@@ -1985,7 +1992,10 @@ export default {
applianceStore.selectedApplianceId,
newBladeId
),
bladeStore.fetchBladeById(applianceStore.selectedApplianceId, newBladeId),
bladeStore.fetchBladeById(
applianceStore.selectedApplianceId,
newBladeId
),
]);
// Update the URL with the new blade ID
updateUrlWithBladeId(applianceStore.selectedApplianceId, newBladeId);
@@ -2063,4 +2073,4 @@ export default {
.highlighted-tab {
font-weight: bold;
}
</style>
</style>
362 changes: 341 additions & 21 deletions webui/src/components/Dashboard/Dashboard.vue
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@
<v-container
style="
width: 100%;
height: 80vh;
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
@@ -14,9 +14,22 @@
{{ currentTitle }}
</h2>

<v-btn @click="toggleGraph" style="margin-bottom: 40px" variant="tonal">
{{ buttonLabel }}
</v-btn>
<v-row>
<v-col>
<v-btn @click="toggleGraph" style="margin-bottom: 40px" variant="tonal">
{{ buttonLabel }}
</v-btn>
</v-col>
<v-col>
<v-btn
@click="discoverDevices"
style="margin-bottom: 40px"
variant="tonal"
>
Click to discover new devices</v-btn
>
</v-col>
</v-row>

<div style="position: relative; width: 25%">
<v-text-field
@@ -34,22 +47,206 @@
{{ "mdi-close" }}
</v-icon>
</div>
<v-card class="parent-card"
style="
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
"
>
<VueFlow
:nodes="nodes"
:edges="edges"
class="basic-flow"
:default-viewport="{ zoom: 1 }"
:min-zoom="0.2"
:max-zoom="4"
@node-click="handleNodeClick"
>
<Controls position="top-left">
<ControlButton title="Search" @click="toggleSearch">
<v-icon>mdi-magnify</v-icon>
</ControlButton>
</Controls>
</VueFlow>
<v-card class="child-card"
style="
width: 20%;
height: 50%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
"
>
<v-card-text>
Devices
<v-list-item>
<v-list-item-title>
<v-icon color="#f2ae72" class="mr-2">mdi-rectangle</v-icon>
CMA
</v-list-item-title>
</v-list-item>
<v-list-item>
<v-list-item-title>
<v-icon color="#f2e394" class="mr-2">mdi-rectangle</v-icon>
Blade
</v-list-item-title>
</v-list-item>
<v-list-item>
<v-list-item-title>
<v-icon color="#d9ecd0" class="mr-2">mdi-rectangle</v-icon>
Host
</v-list-item-title>
</v-list-item>
<br>Status
<v-list-item>
<v-list-item-title>
<v-icon color="#6ebe4a" class="mr-2">mdi-rectangle-outline</v-icon>
online
</v-list-item-title>
</v-list-item>
<v-list-item>
<v-list-item-title>
<v-icon color="#b00020" class="mr-2">mdi-rectangle-outline</v-icon>
offline
</v-list-item-title>
</v-list-item>
<v-list-item>
<v-list-item-title>
<v-icon color="#ff9f40" class="mr-2">mdi-rectangle-outline</v-icon>
unavailable
</v-list-item-title>
</v-list-item>
</v-card-text>
</v-card>
</v-card>

<VueFlow
:nodes="nodes"
:edges="edges"
class="basic-flow"
:default-viewport="{ zoom: 1 }"
:min-zoom="0.2"
:max-zoom="4"
@node-click="handleNodeClick"
>
<Controls position="top-left">
<ControlButton title="Search" @click="toggleSearch">
<v-icon>mdi-magnify</v-icon>
</ControlButton>
</Controls>
</VueFlow>
<!-- The dialog of the warning before the adding the new discovered devices(blades or cxl-hosts) -->
<v-dialog v-model="dialogNewDiscoveredDevices" max-width="600px">
<v-card>
<v-alert
color="info"
icon="$info"
title="New Discovered Devices"
variant="tonal"
text="Here are the discovered devices. You can add them to CFM by selecting them and clicking the 'Add' button."
></v-alert>
<v-card-text class="scrollable-content">
New Discovered Blades:
<v-checkbox
v-for="(blade, index) in discoveredBlades"
:key="index"
:label="`${blade.name} - ${blade.address}`"
v-model="selectedBlades"
:value="blade"
></v-checkbox>
<br>
New Discovered Hosts:
<v-checkbox
v-for="(host, index) in discoveredHosts"
:key="index"
:label="`${host.name} - ${host.address}`"
v-model="selectedHosts"
:value="host"
></v-checkbox>
</v-card-text>
<v-divider></v-divider>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn
color="info"
variant="text"
id="cancelAddSelecteddDevices"
@click="dialogNewDiscoveredDevices = false"
>Cancel</v-btn
>
<v-btn
color="info"
variant="text"
id="confirmAddSelecteddDevices"
@click="addDiscoveredDevices"
>Add</v-btn
>
</v-card-actions>
</v-card>
</v-dialog>

<v-dialog v-model="dialogAddNewDiscoveredDevicesWait">
<v-row align-content="center" class="fill-height" justify="center">
<v-col cols="6">
<v-progress-linear color="#6ebe4a" height="50" indeterminate rounded>
<template v-slot:default>
<div class="text-center">
{{ addNewDiscoveredDevicesProgressText }}
</div>
</template>
</v-progress-linear>
</v-col>
</v-row>
</v-dialog>

<v-dialog v-model="dialogDiscoverDevicesWait">
<v-row align-content="center" class="fill-height" justify="center">
<v-col cols="6">
<v-progress-linear color="#6ebe4a" height="50" indeterminate rounded>
<template v-slot:default>
<div class="text-center">
{{ discoverDevicesProgressText }}
</div>
</template>
</v-progress-linear>
</v-col>
</v-row>
</v-dialog>

<v-dialog v-model="dialogAddNewDiscoveredDevicesOutput" max-width="600px">
<v-sheet
elevation="12"
max-width="600"
rounded="lg"
width="100%"
class="pa-4 text-center mx-auto"
>
<v-icon
class="mb-5"
color="success"
icon="mdi-check-circle"
size="112"
></v-icon>
<h2 class="text-h5 mb-6">Congrats! New devices were added</h2>
<p class="mb-4 text-medium-emphasis text-body-2">
New blades:
<ul>
<li v-for="(blade, index) in newBlades" :key="index">
{{ blade.id }}
</li>
</ul><br />New hosts: <br />
<ul>
<li v-for="(host, index) in newHosts" :key="index">
{{ host.id }}
</li>
</ul>
</p>
<v-divider class="mb-4"></v-divider>
<div class="text-end">
<v-btn
class="text-none"
color="success"
rounded
variant="flat"
width="90"
id="addNewDiscoveredDevicesOutput"
@click="dialogAddNewDiscoveredDevicesOutput = false"
>
Done
</v-btn>
</div>
</v-sheet>
</v-dialog>
</v-container>
</template>

@@ -68,6 +265,110 @@ import { ControlButton, Controls } from "@vue-flow/controls";
export default {
components: { VueFlow, ControlButton, Controls },

data() {
return {
addNewDiscoveredDevicesProgressText:
"Adding the selected devices, please wait...",
discoverDevicesProgressText:"Discovering devices, please wait...",

dialogNewDiscoveredDevices: false,
dialogAddNewDiscoveredDevicesWait: false,
dialogAddNewDiscoveredDevicesOutput: false,
dialogDiscoverDevicesWait: false,

discoveredBlades: [],
discoveredHosts: [],

selectedBlades: [],
selectedHosts: [],

newBlades: [],
newHosts: [],
};
},

methods: {
async discoverDevices() {
this.dialogDiscoverDevicesWait = true;
const applianceStore = useApplianceStore();
const hostStore = useHostStore();

try {
const responseOfBlades = await applianceStore.discoverBlades();
const responseOfHosts = await hostStore.discoverHosts();

const response = (responseOfBlades || []).concat(responseOfHosts || []);
this.discoveredBlades = responseOfBlades || [];
this.discoveredHosts = responseOfHosts || [];

this.dialogNewDiscoveredDevices = true;
this.dialogDiscoverDevicesWait = false;

return response.length ? response : [];
} catch (error) {
this.dialogDiscoverDevicesWait = false;
console.error("Error fetching data:", error);
return [];
}
},

async addDiscoveredDevices() {
this.dialogNewDiscoveredDevices = false;
this.dialogAddNewDiscoveredDevicesWait = true;

const applianceStore = useApplianceStore();
const hostStore = useHostStore();
const bladePortStore = useBladePortStore();

if (this.selectedBlades.length === 0) {
console.log("No blades selected.");
} else {
for (var i = 0; i < this.selectedBlades.length; i++) {
try {
const newAddedBlade = await applianceStore.addDiscoveredBlades(
this.selectedBlades[i]
);

if (newAddedBlade) {
this.newBlades.push(newAddedBlade);
}
} catch (error) {
console.error("Error adding new discovered blade:", error);
}
}
}

if (this.selectedHosts.length === 0) {
console.log("No hosts selected.");
} else {
for (var i = 0; i < this.selectedHosts.length; i++) {
try {
const newAddedHost = await hostStore.addDiscoveredHosts(
this.selectedHosts[i]
);
if (newAddedHost) {
this.newHosts.push(newAddedHost);
}
} catch (error) {
console.error("Error adding new discovered host:", error);
}
}
}

// Update the graph content
await applianceStore.fetchAppliances();
await hostStore.fetchHosts();
for (const appliance of applianceStore.applianceIds) {
for (const blade of appliance.blades) {
await bladePortStore.fetchBladePorts(appliance.id, blade.id);
}
}

this.dialogAddNewDiscoveredDevicesWait = false;
this.dialogAddNewDiscoveredDevicesOutput = true;
},
},

setup() {
const applianceStore = useApplianceStore();
const hostStore = useHostStore();
@@ -178,8 +479,8 @@ export default {
await hostStore.fetchHosts();
// Ensure blade ports are fetched after appliances, this action will create the edges for dataPlane
for (const appliance of applianceStore.applianceIds) {
for (const bladeId of appliance.bladeIds) {
await bladePortStore.fetchBladePorts(appliance.id, bladeId);
for (const blade of appliance.blades) {
await bladePortStore.fetchBladePorts(appliance.id, blade.id);
}
}
});
@@ -200,3 +501,22 @@ export default {
},
};
</script>

<style scoped>
.scrollable-content {
max-height: 300px;
overflow-y: auto;
}

.parent-card {
position: relative;
}

.child-card {
position: absolute;
top: 0;
right: 0;
background-color: #f5f5f5;
border: 1px solid #ccc;
}
</style>
59 changes: 33 additions & 26 deletions webui/src/components/Dashboard/initial-control-elements.ts
Original file line number Diff line number Diff line change
@@ -23,7 +23,7 @@ export const useControlData = () => {
id: "cfm-service",
data: { label: "CFM Service", },
position: position,
style: { backgroundColor: "#6ebe4a", color: "#000" },
style: { backgroundColor: useLayout().Colors.serviceColor, border: "none" },
type: serviceNodeType,
},
]
@@ -35,26 +35,33 @@ export const useControlData = () => {
id: `appliance-${appliance.id}`,
data: { label: appliance.id, url: `/appliances/${appliance.id}` },
position: position,
style: { backgroundColor: "#f2ae72", color: "#000" },
style: { backgroundColor: useLayout().Colors.applianceColor, border: "none" },
type: applianceNodeType,
},
...appliance.bladeIds.map((bladeId, bladeIndex) => ({
id: `blade-${bladeId}`,
data: { label: bladeId, url: `/appliances/${appliance.id}/blades/${bladeId}`, associatedAppliance: appliance.id },
position: position,
style: { backgroundColor: "#f2e394", color: "#000" },
type: bladeNodeType,
})),
...appliance.blades.map((blade, bladeIndex) => {
const borderColor = useLayout().borderColorChange(blade.status);
return {
id: `blade-${blade.id}`,
data: { label: blade.id, url: `/appliances/${appliance.id}/blades/${blade.id}`, associatedAppliance: appliance.id },
position: position,
style: { backgroundColor: useLayout().Colors.baldeColor, border: `3px solid ${borderColor}` },
type: bladeNodeType,
}
}),
]
);

const hostNodes = hostStore.hostIds.map((host, index) => ({
id: `host-${host}`,
data: { label: host, url: `/hosts/${host}` },
position: position,
style: { backgroundColor: "#d9ecd0", color: "#000" },
type: hostNodeType,
}));
const hostNodes = hostStore.hostIds.map((host, index) => {
const borderColor = useLayout().borderColorChange(host.status);

return {
id: `host-${host.id}`,
data: { label: host.id, url: `/hosts/${host.id}` },
position: position,
style: { backgroundColor: useLayout().Colors.hostColor, border: `3px solid ${borderColor}` },
type: hostNodeType,
}
});

const allNodes = [...coreNode, ...applianceNodes, ...hostNodes];

@@ -67,18 +74,18 @@ export const useControlData = () => {
source: "cfm-service",
target: `appliance-${appliance.id}`,
},
...appliance.bladeIds.map((bladeId) => ({
id: `appliance-blade-${appliance.id}-${bladeId}`,
...appliance.blades.map((blade) => ({
id: `appliance-blade-${appliance.id}-${blade.id}`,
source: `appliance-${appliance.id}`,
target: `blade-${bladeId}`,
target: `blade-${blade.id}`,
})),
])
: [];

const hostEdges = hostStore.hostIds.map((host) => ({
id: `cfm-${host}`,
id: `cfm-${host.id}`,
source: "cfm-service",
target: `host-${host}`,
target: `host-${host.id}`,
}));

return [...coreEdges, ...hostEdges];
@@ -97,19 +104,19 @@ export const useControlData = () => {
target: `appliance-${appliance.id}`,
animated: true,
},
...appliance.bladeIds.map((bladeId) => ({
id: `appliance-blade-${appliance.id}-${bladeId}`,
...appliance.blades.map((blade) => ({
id: `appliance-blade-${appliance.id}-${blade.id}`,
source: `appliance-${appliance.id}`,
target: `blade-${bladeId}`,
target: `blade-${blade.id}`,
animated: true,
})),
])
: [];

const hostEdges = hostStore.hostIds.map((host) => ({
id: `cfm-${host}`,
id: `cfm-${host.id}`,
source: "cfm-service",
target: `host-${host}`,
target: `host-${host.id}`,
animated: true,
}));

61 changes: 38 additions & 23 deletions webui/src/components/Dashboard/initial-data-elements.ts
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@ import { computed } from "vue";
import { useApplianceStore } from "../Stores/ApplianceStore";
import { useHostStore } from "../Stores/HostStore";
import { useBladePortStore } from "../Stores/BladePortStore";
import { useLayout } from "./useLayout";

export const useData = () => {
const applianceStore = useApplianceStore();
@@ -17,7 +18,7 @@ export const useData = () => {
let currentYPosition = 0;
const applianceNodes = applianceStore.applianceIds.flatMap(
(appliance, index) => {
const bladeCount = appliance.bladeIds.length;
const bladeCount = appliance.blades.length;
const applianceHeight = 50 + bladeCount * 50; // Adjust height based on number of blades
const applianceWidth = 270; // Width of the appliance node
const bladeWidth = 250; // Width of the blade node
@@ -27,40 +28,54 @@ export const useData = () => {
id: `appliance-${appliance.id}`,
data: { label: appliance.id, url: `/appliances/${appliance.id}` },
position: { x: 100, y: currentYPosition },
style: { backgroundColor: "rgba(242, 174, 114, 0.5)", color: "#000", height: `${applianceHeight}px`, width: `${applianceWidth}px` },
style: { backgroundColor: useLayout().Colors.applianceColor, height: `${applianceHeight}px`, width: `${applianceWidth}px`, border: "none" },
type: applianceNodeType,
sourcePosition: 'right',
targetPosition: 'left',
};

const bladeNodes = appliance.bladeIds.map((bladeId, bladeIndex) => ({
id: `blade-${bladeId}`,
data: { label: bladeId, url: `/appliances/${appliance.id}/blades/${bladeId}`, associatedAppliance: appliance.id },
position: { x: bladeXPosition, y: 50 + bladeIndex * 50 }, // Center blades within the appliance node
style: { backgroundColor: "#f2e394", color: "#000", width: `${bladeWidth}px` },
type: bladeNodeType,
parentNode: `appliance-${appliance.id}`,
extent: 'parent',
expandParent: true,
sourcePosition: 'right',
targetPosition: 'left',
}));
const bladeNodes = appliance.blades.map((blade, bladeIndex) => {
const borderColor = useLayout().borderColorChange(blade.status);
return {
id: `blade-${blade.id}`,
data: { label: blade.id, url: `/appliances/${appliance.id}/blades/${blade.id}`, associatedAppliance: appliance.id },
position: { x: bladeXPosition, y: 50 + bladeIndex * 50 }, // Center blades within the appliance node
style: { backgroundColor: useLayout().Colors.baldeColor, width: `${bladeWidth}px`, border: `3px solid ${borderColor}` },
type: bladeNodeType,
parentNode: `appliance-${appliance.id}`,
extent: 'parent',
expandParent: true,
sourcePosition: 'right',
targetPosition: 'left',
}
});

currentYPosition += applianceHeight + 20; // Add some space between nodes

return [applianceNode, ...bladeNodes];
}
);

const hostNodes = hostStore.hostIds.map((host, index) => ({
id: `host-${host}`,
data: { label: host, url: `/hosts/${host}` },
position: { x: 500, y: index * 200 },
style: { backgroundColor: "#d9ecd0", color: "#000" },
type: hostNodeType,
sourcePosition: 'right',
targetPosition: 'left',
}));
const hostNodes = hostStore.hostIds.map((host, index) => {
const { width, height } = useLayout().measureText(host.id);
const borderColor = useLayout().borderColorChange(host.status);

return {
id: `host-${host.id}`,
data: { label: host.id, url: `/hosts/${host.id}` },
position: { x: 500, y: index * 200 },
style: {
backgroundColor: useLayout().Colors.hostColor,
color: "#000",
width: `${width + 20}px`, // Adding some padding
height: `${height + 20}px`, // Adding some padding
border: `3px solid ${borderColor}`
},
type: hostNodeType,
sourcePosition: 'right',
targetPosition: 'left',
};
});

return [...applianceNodes, ...hostNodes];
});
53 changes: 47 additions & 6 deletions webui/src/components/Dashboard/useLayout.ts
Original file line number Diff line number Diff line change
@@ -7,6 +7,39 @@ import { ref } from 'vue';
* Composable to run the layout algorithm on the graph.
* It uses the `dagre` library to calculate the layout of the nodes and edges.
*/

export function measureText(text: string, font = '16px Arial') {
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
context!.font = font;
const metrics = context!.measureText(text);
const width = Math.max(metrics.width, 220);
return {
width: width,
height: parseInt(font, 10) // Assuming height is roughly the font size
};
};

export const Colors = {
applianceColor: '#f2ae72',
baldeColor: '#f2e394',
hostColor: '#d9ecd0',
serviceColor: '#6ebe4a',
};

export function borderColorChange(status: string | undefined) {
switch (status) {
case "online":
return "#6ebe4a"; // green
case "offline":
return "#b00020"; // Red
case "unavailable":
return "#ff9f40"; // Orange
default:
return "#ffffff"; // White
}
};

export function useLayout() {
const { findNode } = useVueFlow();

@@ -28,10 +61,13 @@ export function useLayout() {
previousDirection.value = direction;

for (const node of nodes) {
// Use the dimensions property of the internal node (`GraphNode` type)
const graphNode = findNode(node.id);
// Measure the text dimensions for dynamic sizing
const { width, height } = measureText(node.data.label);

dagreGraph.setNode(node.id, { width: graphNode?.dimensions.width || 150, height: graphNode?.dimensions.height || 50 });
dagreGraph.setNode(node.id, {
width: width + 20, // Adding some padding
height: height + 20 // Adding some padding
});
}

for (const edge of edges) {
@@ -41,17 +77,22 @@ export function useLayout() {
dagre.layout(dagreGraph);

// Set nodes with updated positions
return nodes.map((node: { id: string | dagre.Label; }) => {
return nodes.map((node: { id: string | dagre.Label; style: any; }) => {
const nodeWithPosition = dagreGraph.node(node.id);

return {
...node,
targetPosition: isHorizontal ? Position.Left : Position.Top,
sourcePosition: isHorizontal ? Position.Right : Position.Bottom,
position: { x: nodeWithPosition.x, y: nodeWithPosition.y },
style: {
...node.style,
width: `${nodeWithPosition.width}px`,
height: `${nodeWithPosition.height}px`
}
};
});
}

return { graph, layout, previousDirection };
}
return { graph, layout, previousDirection, measureText, borderColorChange, Colors };
}
99 changes: 93 additions & 6 deletions webui/src/components/Stores/ApplianceStore.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Copyright (c) 2024 Seagate Technology LLC and/or its Affiliates
import { defineStore } from 'pinia'
import { Appliance, Credentials, DefaultApi } from "@/axios/api";
import { Appliance, Credentials, DefaultApi, DiscoveredDevice } from "@/axios/api";
import { BASE_PATH } from "@/axios/base";
import axios from 'axios';

@@ -14,7 +14,30 @@ export const useApplianceStore = defineStore('appliance', {
addApplianceError: null as unknown,
deleteApplianceError: null as unknown,
renameApplianceError: null as unknown,
applianceIds: [] as { id: string, bladeIds: string[] }[],
applianceIds: [] as { id: string, blades: { id: string, ipAddress: string, status: string | undefined }[] }[],
discoveredBlades: [] as DiscoveredDevice[],

prefixBladeId: "Discoverd_Blade_",
newBladeCredentials: {
username: "root",
password: "0penBmc",
ipAddress: "127.0.0.1",
port: 443,
insecure: true,
protocol: "https",
customId: "",
},

defaultApplianceId: "CMA_Discovered_Blades",
newApplianceCredentials: {
username: "root",
password: "0penBmc",
ipAddress: "127.0.0.1",
port: 8443,
insecure: true,
protocol: "https",
customId: "",
},
}),

actions: {
@@ -72,32 +95,96 @@ export const useApplianceStore = defineStore('appliance', {

const responseOfBlades = await defaultApi.bladesGet(applianceId);
const bladeCount = responseOfBlades.data.memberCount;
const bladeIds = [];
const associatedBlades = [];

for (let i = 0; i < bladeCount; i++) {
// Extract the id for each blade
const uri = responseOfBlades.data.members[i];
const bladeId: string = JSON.stringify(uri).split("/").pop()?.slice(0, -2) as string;
// Store blade in blades
if (bladeId) {
bladeIds.push(bladeId);
const responseOfBlade = await defaultApi.bladesGetById(applianceId, bladeId);
const response = { id: responseOfBlade.data.id, ipAddress: responseOfBlade.data.ipAddress, status: responseOfBlade.data.status }
associatedBlades.push(response);
}
}
this.applianceIds.push({ id: detailsResponseOfAppliance.data.id, bladeIds });
this.applianceIds.push({ id: detailsResponseOfAppliance.data.id, blades: associatedBlades });
}
}
} catch (error) {
console.error("Error fetching appliances:", error);
}
},

async discoverBlades() {
try {
// Get all the existed blades
const existedBladeIpAddress: (string | undefined)[] = []
for (var i = 0; i < this.applianceIds.length; i++) {
for (var j = 0; j < this.applianceIds[i].blades.length; j++) {
existedBladeIpAddress.push(this.applianceIds[i].blades[j].ipAddress)
}
}

const defaultApi = new DefaultApi(undefined, API_BASE_PATH);
this.discoveredBlades = [];
const responseOfBlade = await defaultApi.discoverDevices("blade");
this.discoveredBlades = responseOfBlade.data;

// Remove the existed blades from the discovered blades
for (var k = 0; k < this.discoveredBlades.length; k++) {
for (var m = 0; m < existedBladeIpAddress.length; m++) {
this.discoveredBlades = this.discoveredBlades.filter(
(discoveredBlade) => discoveredBlade.address !== existedBladeIpAddress[m]
);
}
}

return this.discoveredBlades
} catch (error) {
console.error("Error discovering new devices:", error);
}
},

async addDiscoveredBlades(blade: DiscoveredDevice) {
const defaultApi = new DefaultApi(undefined, API_BASE_PATH);
const responseOfApplianceExist = await defaultApi.appliancesGetById(this.defaultApplianceId)

// If there is no default appliance, add one
if (!responseOfApplianceExist) {
this.newApplianceCredentials.customId = this.defaultApplianceId;
const responseOfAppliance = await defaultApi.appliancesPost(this.newApplianceCredentials);

// Add the new appliance to the appliances and applianceIds array
if (responseOfAppliance) {
this.appliances.push(responseOfAppliance.data);
const newAppliance = { id: responseOfAppliance.data.id, blades: [] }
this.applianceIds.push(newAppliance)
}
}

// Add the new discovered blade to the default appliance
let appliance = this.applianceIds.find(appliance => appliance.id === this.defaultApplianceId);

this.newBladeCredentials.customId = this.prefixBladeId + blade.address;
this.newBladeCredentials.ipAddress = blade.address + "";

const responseOfBlade = await defaultApi.bladesPost(this.defaultApplianceId, this.newBladeCredentials);

if (responseOfBlade) {
const response = { id: responseOfBlade.data.id, ipAddress: responseOfBlade.data.ipAddress, status: responseOfBlade.data.status };
appliance!.blades.push(response);
}

return responseOfBlade.data;
},

async addNewAppliance(newAppliance: Credentials) {
this.addApplianceError = "";
try {
const defaultApi = new DefaultApi(undefined, API_BASE_PATH);
const response = await defaultApi.appliancesPost(newAppliance);
const addedAppliance = response.data;
console.log("added appliance", addedAppliance)
// Add the new appliance to the appliances array
this.appliances.push(addedAppliance);
return addedAppliance;
64 changes: 61 additions & 3 deletions webui/src/components/Stores/HostStore.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Copyright (c) 2024 Seagate Technology LLC and/or its Affiliates
import { defineStore } from 'pinia'
import { Host, Credentials, DefaultApi } from "@/axios/api";
import { Host, Credentials, DefaultApi, DiscoveredDevice } from "@/axios/api";
import { BASE_PATH } from "@/axios/base";
import axios from 'axios';

@@ -19,7 +19,19 @@ export const useHostStore = defineStore('host', {
deleteHostError: null as unknown,
resyncHostError: null as unknown,
renameHostError: null as unknown,
hostIds: [] as string[],
hostIds: [] as { id: string, ipAddress: string, status: string | undefined }[],
discoveredHosts: [] as DiscoveredDevice[],

prefixHostId: "Discoverd_Host_",
newHostCredentials: {
username: "admin",
password: "admin12345",
ipAddress: "127.0.0.1",
port: 8082,
insecure: true,
protocol: "http",
customId: "",
},
}),

actions: {
@@ -44,7 +56,8 @@ export const useHostStore = defineStore('host', {
// Store host in hosts
if (detailsResponseOfHost) {
this.hosts.push(detailsResponseOfHost.data);
this.hostIds.push(detailsResponseOfHost.data.id)
const host = { id: detailsResponseOfHost.data.id, ipAddress: detailsResponseOfHost.data.ipAddress, status: detailsResponseOfHost.data.status }
this.hostIds.push(host)
}
}
} catch (error) {
@@ -68,6 +81,51 @@ export const useHostStore = defineStore('host', {
}
},

async discoverHosts() {
try {
// Get all the existed hosts
const existedHostIpAddress: (string | undefined)[] = []
for (var i = 0; i < this.hostIds.length; i++) {
existedHostIpAddress.push(this.hostIds[i].ipAddress)
}

const defaultApi = new DefaultApi(undefined, API_BASE_PATH);
this.discoveredHosts = [];
const responseOfHost = await defaultApi.discoverDevices("cxl-host");
this.discoveredHosts = responseOfHost.data;

// Remove the existed hosts from the discovered hosts
for (var k = 0; k < this.discoveredHosts.length; k++) {
for (var m = 0; m < existedHostIpAddress.length; m++) {
this.discoveredHosts = this.discoveredHosts.filter(
(discoveredHost) => discoveredHost.address !== existedHostIpAddress[m]
);
}
}

return this.discoveredHosts
} catch (error) {
console.error("Error discovering new devices:", error);
}
},

async addDiscoveredHosts(host: DiscoveredDevice) {
const defaultApi = new DefaultApi(undefined, API_BASE_PATH);
// Add all the new didcovered hosts
this.newHostCredentials.customId = this.prefixHostId + host.address;
this.newHostCredentials.ipAddress = host.address + "";

const responseOfHost = await defaultApi.hostsPost(this.newHostCredentials);

// Update the applianceIds and appliances
if (responseOfHost) {
const response = { id: responseOfHost.data.id, ipAddress: responseOfHost.data.ipAddress, status: responseOfHost.data.status }
this.hosts.push(responseOfHost.data);
this.hostIds.push(response)
}
return responseOfHost.data;
},

async addNewHost(newHost: Credentials) {
this.addHostError = "";
try {