diff --git a/jccm/src/Frontend/Common/StateStore.js b/jccm/src/Frontend/Common/StateStore.js
index 7cf08cf..1c92342 100644
--- a/jccm/src/Frontend/Common/StateStore.js
+++ b/jccm/src/Frontend/Common/StateStore.js
@@ -59,6 +59,9 @@ const useStore = create((set, get) => ({
isUserLoggedIn: false,
setIsUserLoggedIn: (isUserLoggedIn) => set(() => ({ isUserLoggedIn })),
+ isInventoryLoading: false,
+ setIsInventoryLoading: (isInventoryLoading) => set(() => ({ isInventoryLoading })),
+
user: null,
orgs: {},
setUser: (user) =>
diff --git a/jccm/src/Frontend/Components/Login.js b/jccm/src/Frontend/Components/Login.js
index e7f624d..a609d53 100644
--- a/jccm/src/Frontend/Components/Login.js
+++ b/jccm/src/Frontend/Components/Login.js
@@ -205,10 +205,14 @@ export const Login = ({ isOpen, onClose }) => {
return { status: 'two_factor' };
} else {
console.log('Login successful!');
+ await eventBus.emit('cloud-inventory-refresh');
+
return { status: 'success', message: 'Login successful!', data: data };
}
} else {
console.log('Login failed!');
+ await eventBus.emit('cloud-inventory-refresh');
+
return { status: 'error', message: 'Login failed!' };
}
} catch (error) {
@@ -234,8 +238,6 @@ export const Login = ({ isOpen, onClose }) => {
setIsUserLoggedIn(true);
setCurrentActiveThemeName(Constants.getActiveThemeName(data?.user?.theme));
- setCloudInventory(data.inventory);
- setCloudInventoryFilterApplied(data.isFilterApplied);
onClose();
} else if (response.status === 'two_factor') {
@@ -315,8 +317,6 @@ export const Login = ({ isOpen, onClose }) => {
setIsUserLoggedIn(true);
setCurrentActiveThemeName(Constants.getActiveThemeName(data?.user?.theme));
- setCloudInventory(data.inventory);
- setCloudInventoryFilterApplied(data.isFilterApplied);
onClose();
} else {
diff --git a/jccm/src/Frontend/Components/UserAvatar.js b/jccm/src/Frontend/Components/UserAvatar.js
index f95ad5b..a7af02d 100644
--- a/jccm/src/Frontend/Components/UserAvatar.js
+++ b/jccm/src/Frontend/Components/UserAvatar.js
@@ -22,7 +22,7 @@ const UserAvatar = () => {
useEffect(() => {
const intervalId = setInterval(async () => {
- await eventBus.emit('user-session-check', { message: ':Periodic user session aliveness check' });
+ await eventBus.emit('user-session-check', { message: 'Periodic user session aliveness check' });
}, 30000);
return () => clearInterval(intervalId);
}, []);
diff --git a/jccm/src/Frontend/Layout/InventoryTreeMenuLocal.js b/jccm/src/Frontend/Layout/InventoryTreeMenuLocal.js
index 7fe658a..c5d5f47 100644
--- a/jccm/src/Frontend/Layout/InventoryTreeMenuLocal.js
+++ b/jccm/src/Frontend/Layout/InventoryTreeMenuLocal.js
@@ -909,6 +909,12 @@ const InventoryTreeMenuLocal = () => {
return siteExists && device.path.startsWith(node.value) && !!!cloudDevices[serialNumber];
});
+ const targetOrgs = new Set();
+
+ targetDevices.forEach((device) => {
+ targetOrgs.add(device.organization);
+ });
+
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const rateLimit = 1000 / rate; // Rate in calls per second
const maxConcurrentCalls = 100; // Maximum number of concurrent async calls
@@ -946,7 +952,7 @@ const InventoryTreeMenuLocal = () => {
await adoptDeviceFactsWithRateLimit();
setTimeout(async () => {
- await eventBus.emit('cloud-inventory-refresh', { notification: false });
+ await eventBus.emit('cloud-inventory-refresh', { targetOrgs: Array.from(targetOrgs), notification: false });
}, 3000);
};
@@ -1006,6 +1012,12 @@ const InventoryTreeMenuLocal = () => {
return device.path.startsWith(node.value) && !!cloudDevices[serialNumber];
});
+ const targetOrgs = new Set();
+
+ targetDevices.forEach((device) => {
+ targetOrgs.add(device.organization);
+ });
+
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const rateLimit = 1000 / rate; // Rate in calls per second
const maxConcurrentCalls = 100; // Maximum number of concurrent async calls
@@ -1042,7 +1054,7 @@ const InventoryTreeMenuLocal = () => {
await releaseDeviceFactsWithRateLimit();
setTimeout(async () => {
- await eventBus.emit('cloud-inventory-refresh', { notification: false });
+ await eventBus.emit('cloud-inventory-refresh', { targetOrgs: Array.from(targetOrgs), notification: false });
}, 3000);
};
diff --git a/jccm/src/Frontend/Layout/LeftSide.js b/jccm/src/Frontend/Layout/LeftSide.js
index 7f90695..e0cae6f 100644
--- a/jccm/src/Frontend/Layout/LeftSide.js
+++ b/jccm/src/Frontend/Layout/LeftSide.js
@@ -51,6 +51,7 @@ import InventoryTreeMenuCloud from './InventoryTreeMenuCloud';
import OrgFilterMenu from './OrgFilterMenu';
import InventoryTreeMenuLocal from './InventoryTreeMenuLocal';
import eventBus from '../Common/eventBus';
+import { RotatingIcon } from './ChangeIcon';
const StackIcon = bundleIcon(StackFilled, StackRegular);
const CloudIcon = bundleIcon(CloudFilled, CloudRegular);
@@ -75,6 +76,7 @@ const LeftSide = () => {
cloudInventoryFilterApplied,
setCloudInventoryFilterApplied,
currentActiveThemeName,
+ isInventoryLoading,
} = useStore();
const [selectedTree, setSelectedTree] = useState('local');
@@ -142,13 +144,40 @@ const LeftSide = () => {
Local
{isUserLoggedIn ? (
- }
+
- Cloud
-
+
}
+ >
+ Cloud
+
+ {isInventoryLoading && (
+
+
+ Loading large cloud inventory...
+
+ )}
+
) : (
{
const { importSettings, settings } = useStore();
- const { isUserLoggedIn, setIsUserLoggedIn, user, setUser } = useStore();
+ const { isUserLoggedIn, setIsUserLoggedIn, user, setUser, setIsInventoryLoading } = useStore();
const { inventory, setInventory } = useStore();
const { cloudInventory, setCloudInventory } = useStore();
const { deviceFacts, setDeviceFactsAll, cleanUpDeviceFacts, zeroDeviceFacts } = useStore();
@@ -62,14 +62,22 @@ export const MainEventProcessor = () => {
}
};
- const handleCloudInventoryRefresh = async ({ notification = false } = {}) => {
+ const handleCloudInventoryRefresh = async ({ targetOrgs = null, notification = false } = {}) => {
console.log('Event: "cloud-inventory-refresh"');
- const response = await electronAPI.saGetCloudInventory();
+ // Initialize a timeout for setting the loading state
+ const loadingTimeout = setTimeout(() => {
+ setIsInventoryLoading(true);
+ }, 3000);
+
+ const response = await electronAPI.saGetCloudInventory({targetOrgs});
+
+ clearTimeout(loadingTimeout);
+ setIsInventoryLoading(false);
if (response.cloudInventory) {
if (!_.isEqual(cloudInventoryRef.current, response.inventory)) {
- console.log('>>>cloudInventory', response.inventory);
+ // console.log('>>>cloudInventory', response.inventory);
setCloudInventory(response.inventory);
setCloudInventoryFilterApplied(response.isFilterApplied);
}
@@ -119,11 +127,10 @@ export const MainEventProcessor = () => {
};
const handleUserSessionCheck = async ({ message = '' } = {}) => {
- console.log(`Event: "user-session-check" ${message}`);
+ console.log(`Event: "user-session-check" ${message.length > 0 ? `-> "${message}"` : ''}`);
try {
const data = await electronAPI.saWhoamiUser();
if (data.sessionValid) {
- // console.log('user-session-check: data', data);
if (!_.isEqual(userRef.current, data.user)) {
setUser(data.user);
setIsUserLoggedIn(true);
@@ -131,26 +138,24 @@ export const MainEventProcessor = () => {
if (currentActiveThemeNameRef.current !== data?.user?.theme) {
setCurrentActiveThemeName(data.user.theme);
}
- if (!_.isEqual(cloudInventoryRef.current, data?.inventory)) {
- setCloudInventory(data.inventory);
- setCloudInventoryFilterApplied(data.isFilterApplied);
- }
+
+ await handleCloudInventoryRefresh();
} else {
setUser(null);
- setCloudInventory([])
+ setCloudInventory([]);
setIsUserLoggedIn(false);
setCurrentActiveThemeName(data.theme);
}
} catch (error) {
setUser(null);
- setCloudInventory([])
+ setCloudInventory([]);
setIsUserLoggedIn(false);
console.error('Session check error:', error);
}
};
const handleDeviceFactsRefresh = async () => {
- console.log('handleDeviceFactsRefresh');
+ console.log('Event: "device-facts-refresh"');
const data = await electronAPI.saLoadDeviceFacts();
if (data.deviceFacts) {
@@ -161,7 +166,7 @@ export const MainEventProcessor = () => {
};
const handleDeviceFactsCleanup = async () => {
- console.log('handleDeviceFactsCleanup');
+ console.log('Event: "device-facts-cleanup"');
cleanUpDeviceFacts();
};
diff --git a/jccm/src/Services/ApiServer copy.js b/jccm/src/Services/ApiServer copy.js
new file mode 100644
index 0000000..e13cbc8
--- /dev/null
+++ b/jccm/src/Services/ApiServer copy.js
@@ -0,0 +1,645 @@
+import { ipcMain, BrowserWindow, session, screen } from 'electron';
+import { getCloudInfoMinVersion } from '../config';
+import { Client } from 'ssh2';
+
+import { mainWindow } from '../main.js';
+
+import {
+ msGetActiveCloud,
+ msGetActiveRegionName,
+ msGetTheme,
+ msSetTheme,
+ msSetCloudInventory,
+ msGetCloudInventory,
+ msSetActiveCloud,
+ msSetActiveRegionName,
+ msSetOrgFilter,
+ msGetOrgFilter,
+ msGetUserEmail,
+ msSetLocalInventory,
+ msGetLocalInventory,
+ msGetCloudOrgs,
+ msSetCloudOrgs,
+ msLoadDeviceFacts,
+ msSaveDeviceFacts,
+ msLoadSubnets,
+ msSaveSubnets,
+ msLoadSettings,
+ msSaveSettings,
+} from './mainStore';
+import {
+ acLookupRegions,
+ acUserLogin,
+ acUserMFA,
+ acUserLogout,
+ acUserSelf,
+ acGetCloudSites,
+ acGetCloudInventory,
+ acGetDeviceStatsType,
+ acRequest,
+ acGetGoogleSSOAuthorizationUrl,
+ acLoginUserGoogleSSO,
+} from './ApiCalls';
+
+import { CloudInfo } from '../config';
+import { commitJunosSetConfig, executeJunosCommand, getDeviceFacts } from './Device';
+const sshSessions = {};
+
+const serverGetCloudInventory = async (targetOrgs = null) => {
+ console.log('main: serverGetCloudInventory');
+
+ const orgFilters = await msGetOrgFilter();
+ const selfData = await acUserSelf();
+
+ if (selfData.status === 'error') {
+ console.error('main: msGetCloudInventory failed', selfData.error);
+ return { inventory: [], isFilterApplied: false };
+ }
+
+ const inventory = [];
+ const orgs = {};
+
+ for (const v of selfData.data.privileges) {
+ if (v.scope === 'org') {
+ const orgId = v.org_id;
+ const orgName = v.name;
+
+ if (!!orgFilters[orgId]) continue;
+
+ const item = { name: orgName, id: orgId };
+
+ const sitesData = await acGetCloudSites(orgId);
+ if (sitesData.status === 'error') {
+ console.error(`serverGetCloudInventory: acGetCloudSites error on org ${orgId}`);
+ continue;
+ }
+
+ const sites = Object.fromEntries(
+ Object.entries(sitesData.data).map(([key, value]) => [value.name, { id: value.id }])
+ );
+
+ orgs[orgName] = { id: v.org_id, sites };
+
+ if (sitesData.status === 'success') {
+ item.sites = sitesData.data;
+ }
+ const inventoryData = await acGetCloudInventory(orgId);
+
+ if (inventoryData.status === 'success') {
+ const devices = inventoryData.data;
+ const siteIdHavingVMAC = new Set();
+
+ try {
+ for (const device of devices) {
+ if (device.site_id) {
+ for (const site of item['sites'] || []) {
+ if (site.id === device.site_id) {
+ device.site_name = site.name;
+ }
+ }
+ }
+ device.org_name = item.name;
+ if (device?.mac.toUpperCase() === device.serial.toUpperCase() && device.type === 'switch') {
+ siteIdHavingVMAC.add(device.site_id);
+ }
+ }
+ } catch (error) {
+ console.error('serverGetCloudInventory: ', error);
+ }
+
+ if (siteIdHavingVMAC.size > 0) {
+ const SN2VSN = {};
+ for (const siteId of siteIdHavingVMAC) {
+ console.log(`Get device stats for site(${siteId})`);
+ const response = await acGetDeviceStatsType(siteId, 'switch');
+
+ if (response.status === 'success') {
+ const cloudDeviceStates = response.data;
+ for (const cloudDevice of cloudDeviceStates) {
+ SN2VSN[cloudDevice.serial] = cloudDevice?.module_stat[0];
+ }
+ }
+ }
+
+ for (const device of devices) {
+ if (device?.mac.toUpperCase() === device.serial.toUpperCase() && device.type === 'switch') {
+ module = SN2VSN[device.serial];
+ device.original_mac = module?.mac;
+ device.original_serial = module?.serial;
+ device.is_vmac_enabled = true;
+ console.log(
+ `switch device: ${device.hostname}, ${device.original_serial} -> ${device.serial}`
+ );
+ }
+ }
+ }
+
+ item.inventory = devices;
+ }
+ inventory.push(item);
+ }
+ }
+
+ // Print out the cloud inventory
+ // console.log(JSON.stringify(inventory, null, 2));
+ // console.log(JSON.stringify(orgs, null, 2));
+
+ await msSetCloudInventory(inventory);
+ await msSetCloudOrgs(orgs);
+
+ const isFilterApplied = Object.keys(orgFilters).length > 0;
+ return { inventory, isFilterApplied };
+};
+
+export const setupApiHandlers = () => {
+ ipcMain.handle('saFetchAvailableClouds', async (event) => {
+ console.log('main: saFetchAvailableClouds');
+ const clouds = getCloudInfoMinVersion();
+ return { clouds };
+ });
+
+ ipcMain.handle('saLookupApiEndpoint', async (event, args) => {
+ console.log('main: saLookupApiEndpoint');
+ const cloudId = args.cloud;
+ const email = args.email;
+ try {
+ const response = await acLookupRegions(cloudId, email);
+ return response;
+ } catch (error) {
+ console.error('User lookup failed!', error);
+ return { status: 'error', error };
+ }
+ });
+
+ ipcMain.handle('saLoginUser', async (event, args) => {
+ console.log('main: saLoginUser');
+
+ const cloudId = args.cloud;
+ const regionName = args.region;
+ const email = args.email;
+ const password = args.password || null;
+ const passcode = args.passcode || null;
+
+ if (!regionName || !email) return { login: 'error', error: 'Missing required fields' };
+ if (!password && !passcode) return { login: 'error', error: 'Missing authentication credentials' };
+
+ if (password && !passcode) {
+ const response = await acUserLogin(cloudId, regionName, email, password);
+
+ if (response.status === 'success') {
+ if (response.data.two_factor_required && !response.data.two_factor_passed) {
+ return { login: true, two_factor: true };
+ } else {
+ const cloudDescription = CloudInfo[cloudId].description;
+ const service = `${cloudDescription}/${regionName}`;
+ const theme = await msGetTheme();
+ return {
+ login: true,
+ user: { ...response.data, service, theme, cloudDescription, regionName },
+ };
+ }
+ }
+ } else if (passcode) {
+ const response = await acUserMFA(passcode);
+
+ if (response.status === 'success') {
+ if (response.data.two_factor_verified) {
+ const cloudDescription = CloudInfo[cloudId].description;
+ const service = `${cloudDescription}/${regionName}`;
+ const theme = await msGetTheme();
+
+ return {
+ login: true,
+ user: { ...response.data, service, theme, cloudDescription, regionName },
+ };
+ } else {
+ return { login: false, error: 'two factor auth failed' };
+ }
+ }
+ } else {
+ if (!password && !passcode) return { login: 'error', error: 'Missing authentication credentials.' };
+ }
+ });
+
+ ipcMain.handle('saLogoutUser', async (event) => {
+ console.log('main: saLogoutUser');
+ try {
+ await acUserLogout();
+ return { logout: true };
+ } catch (error) {
+ return { logout: false, error };
+ }
+ });
+
+ ipcMain.handle('saWhoamiUser', async (event) => {
+ console.log('main: saWhoamiUser');
+ try {
+ const response = await acUserSelf();
+
+ if (response.status === 'success') {
+ const cloudId = await msGetActiveCloud();
+ const cloudDescription = CloudInfo[cloudId].description;
+ const regionName = await msGetActiveRegionName();
+ const service = `${cloudDescription}/${regionName}`;
+ const theme = await msGetTheme();
+
+ // const { inventory, isFilterApplied } = await serverGetCloudInventory();
+
+ return {
+ sessionValid: true,
+ user: { ...response.data, service, theme, cloudDescription, regionName },
+ // inventory,
+ // isFilterApplied,
+ };
+ } else {
+ const theme = await msGetTheme();
+ return { sessionValid: false, error: 'whoami call failed', theme };
+ }
+ } catch (error) {
+ console.log('main: saWhoamiUser: error:', error);
+ return { sessionValid: false, error };
+ }
+ });
+
+ ipcMain.handle('saSetThemeUser', async (event, args) => {
+ console.log('main: saSetThemeUser', args);
+ const theme = args.theme;
+ await msSetTheme(theme);
+ });
+
+ ipcMain.handle('saGetCloudInventory', async (event, args = {}) => {
+ console.log('main: saGetCloudInventory');
+ const { targetOrgs = null } = args;
+
+ const { inventory, isFilterApplied } = await serverGetCloudInventory(targetOrgs);
+
+ return { cloudInventory: true, inventory, isFilterApplied };
+ });
+
+ ipcMain.handle('saProxyCall', async (event, args) => {
+ console.log('main: saProxyCall');
+ const { method, api, body } = args;
+
+ try {
+ const response = await acRequest(api, method, body);
+ return { proxy: true, response };
+ } catch (error) {
+ return { proxy: false, error };
+ }
+ });
+
+ ipcMain.handle('saOrgFilter', async (event, args) => {
+ console.log('main: saOrgFilter');
+ const { method, body } = args;
+
+ if (method === 'GET') {
+ const selfData = await acUserSelf();
+ if (selfData.status === 'error') {
+ return [];
+ }
+
+ const orgs = [];
+ const filters = await msGetOrgFilter();
+
+ const cloudId = await msGetActiveCloud();
+ const cloudDescription = CloudInfo[cloudId].description;
+ const regionName = await msGetActiveRegionName();
+ const userEmail = await msGetUserEmail();
+ const path = `${userEmail}/${cloudDescription}/${regionName}`;
+
+ for (const v of selfData.data.privileges) {
+ if (v.scope === 'org') {
+ const orgId = v.org_id;
+ const item = { name: v.name, id: orgId, path };
+ orgs.push(item);
+ }
+ }
+
+ return { orgFilter: true, orgs, filters };
+ } else if (method === 'SET') {
+ const filters = body.filters;
+ msSetOrgFilter(filters);
+ return { orgFilter: true };
+ } else {
+ return { orgFilter: false, error: `Unknown method: "${method}"` };
+ }
+ });
+
+ ipcMain.handle('saGetLocalInventory', async (event) => {
+ console.log('main: saGetLocalInventory');
+ const inventory = await msGetLocalInventory();
+
+ return { localInventory: true, inventory };
+ });
+
+ ipcMain.handle('saSetLocalInventory', async (event, args) => {
+ console.log('main: saSetLocalInventory');
+ const inventory = args.inventory;
+ await msSetLocalInventory(inventory);
+
+ return { localInventory: true };
+ });
+
+ // SSH handling
+ ipcMain.on('startSSHConnection', async (event, { id, cols, rows }) => {
+ console.log('main: startSSHConnection: id: ' + id);
+ const inventory = await msGetLocalInventory();
+
+ const found = inventory.filter(
+ ({ organization, site, address, port }) => id === `/Inventory/${organization}/${site}/${address}/${port}`
+ );
+ if (found.length === 0) {
+ console.error('No device found: path id: ', id);
+ return;
+ }
+ const device = found[0];
+ const { address, port, username, password } = device;
+
+ const conn = new Client();
+ sshSessions[id] = conn;
+
+ conn.on('ready', () => {
+ console.log(`SSH session successfully opened for id: ${id}`);
+ event.reply('sshSessionOpened', { id }); // Notify renderer that the session is open
+
+ conn.shell({ cols, rows }, (err, stream) => {
+ if (err) {
+ event.reply('sshErrorOccurred', { id, message: err.message });
+ return;
+ }
+
+ stream.on('data', (data) => {
+ event.reply('sshDataReceived', { id, data: data.toString() });
+ });
+
+ stream.on('close', () => {
+ event.reply('sshDataReceived', { id, data: 'The SSH session has been closed.\r\n' });
+
+ conn.end();
+ delete sshSessions[id];
+ event.reply('sshSessionClosed', { id });
+ // Clean up listeners
+ ipcMain.removeAllListeners(`sendSSHInput-${id}`);
+ ipcMain.removeAllListeners(`resizeSSHSession-${id}`);
+ });
+
+ ipcMain.on(`sendSSHInput-${id}`, (_, data) => {
+ stream.write(data);
+ });
+
+ ipcMain.on(`resizeSSHSession-${id}`, (_, { cols, rows }) => {
+ stream.setWindow(rows, cols, 0, 0);
+ });
+ });
+ }).connect({
+ host: address,
+ port,
+ username,
+ password,
+ poll: 10, // Adjust the polling interval to 10 milliseconds
+ keepaliveInterval: 10000, // Send keepalive every 10 seconds
+ keepaliveCountMax: 3, // Close the connection after 3 failed keepalives
+ });
+
+ conn.on('error', (err) => {
+ event.reply('sshErrorOccurred', { id, message: err.message });
+ });
+
+ conn.on('end', () => {
+ delete sshSessions[id];
+ });
+ });
+
+ ipcMain.on('disconnectSSHSession', (event, { id }) => {
+ console.log('main: disconnectSSHSession');
+
+ if (sshSessions[id]) {
+ sshSessions[id].end();
+ delete sshSessions[id];
+ }
+ });
+
+ // IPC main handler to adopt the device
+ ipcMain.handle('saAdoptDevice', async (event, args) => {
+ console.log('main: saAdoptDevice');
+
+ const { organization, site, address, port, username, password, jsiTerm, deleteOutboundSSHTerm, ...others } =
+ args;
+
+ const cloudOrgs = await msGetCloudOrgs();
+ const orgId = cloudOrgs[organization]?.id;
+ const siteId = cloudOrgs[organization]?.sites?.[site]?.id || null;
+
+ try {
+ let endpoint = 'ocdevices';
+ if (jsiTerm) {
+ endpoint = 'jsi/devices';
+ }
+
+ const api = `orgs/${orgId}/${endpoint}/outbound_ssh_cmd${siteId ? `?site_id=${siteId}` : ''}`;
+ const response = await acRequest(api, 'GET', null);
+
+ const configCommand = deleteOutboundSSHTerm
+ ? `delete system services outbound-ssh\n${response.cmd}\n`
+ : `${response.cmd}\n`;
+
+ const reply = await commitJunosSetConfig(address, port, username, password, configCommand);
+
+ if (reply.status === 'success' && reply.data.includes('')) {
+ return { adopt: true, reply };
+ } else {
+ return { adopt: false, reply };
+ }
+ } catch (error) {
+ console.error('Configuration failed!', error);
+ return { adopt: false, reply: error };
+ }
+ });
+
+ ipcMain.handle('saReleaseDevice', async (event, args) => {
+ console.log('main: saReleaseDevice');
+
+ const { organization, serial } = args;
+
+ const cloudOrgs = await msGetCloudOrgs();
+ const orgId = cloudOrgs[organization]?.id;
+
+ try {
+ console.log('device releasing!');
+
+ const serialsPayload = typeof serial === 'string' ? [serial] : serial;
+
+ const response = await acRequest(`orgs/${orgId}/inventory`, 'PUT', {
+ op: 'delete',
+ serials: serialsPayload,
+ });
+
+ return { release: true, reply: response };
+ } catch (error) {
+ console.error('Configuration failed!', error);
+ return { release: false, reply: error };
+ }
+ });
+
+ ipcMain.handle('saExecuteJunosCommand', async (event, args) => {
+ console.log('main: saExecuteJunosCommand');
+
+ try {
+ const { address, port, username, password, command, timeout } = args;
+ const reply = await executeJunosCommand(address, port, username, password, command, timeout);
+ return { command: true, reply };
+ } catch (error) {
+ console.error('Junos command execution failed!', error);
+ return { command: false, reply };
+ }
+ });
+
+ ipcMain.handle('saLoadDeviceFacts', async (event) => {
+ console.log('main: saLoadDeviceFacts');
+ const facts = await msLoadDeviceFacts();
+
+ return { deviceFacts: true, facts };
+ });
+
+ ipcMain.handle('saSaveDeviceFacts', async (event, args) => {
+ console.log('main: saSaveDeviceFacts');
+ const facts = args.facts;
+ await msSaveDeviceFacts(facts);
+
+ return { deviceFacts: true };
+ });
+
+ ipcMain.handle('saLoadSubnets', async (event) => {
+ console.log('main: saLoadSubnets');
+ const subnets = await msLoadSubnets();
+
+ return { status: true, subnets };
+ });
+
+ ipcMain.handle('saSaveSubnets', async (event, args) => {
+ console.log('main: saSaveSubnets');
+ const subnets = args.subnets;
+ await msSaveSubnets(subnets);
+ console.log('main: saSaveSubnets:', subnets);
+
+ return { status: true };
+ });
+
+ ipcMain.handle('saLoadSettings', async (event) => {
+ console.log('main: saLoadSettings');
+ const settings = await msLoadSettings();
+
+ return { status: true, settings };
+ });
+
+ ipcMain.handle('saSaveSettings', async (event, args) => {
+ console.log('main: saSaveSettings');
+ const settings = args.settings;
+ await msSaveSettings(settings);
+ console.log('main: saSaveSettings:', settings);
+
+ return { status: true };
+ });
+
+ ipcMain.handle('saGetDeviceFacts', async (event, args) => {
+ console.log('main: saGetDeviceFacts');
+
+ try {
+ const { address, port, username, password, timeout, upperSerialNumber } = args;
+ const reply = await getDeviceFacts(address, port, username, password, timeout, upperSerialNumber);
+
+ return { facts: true, reply };
+ } catch (error) {
+ // console.error('saGetDeviceFacts: Junos command execution failed!', args, error);
+ return { facts: false, reply: error };
+ }
+ });
+
+ ipcMain.handle('saGetGoogleSSOAuthCode', async (event, args) => {
+ console.log('main: saGetGoogleSSOAuthCode');
+
+ const cloudId = args.cloud;
+ const regionName = args.region;
+
+ if (!cloudId || !regionName) {
+ return { login: 'error', error: 'Missing required fields' };
+ }
+
+ const response = await acGetGoogleSSOAuthorizationUrl(cloudId, regionName);
+
+ if (response.status === 'success') {
+ const authorizationUrl = response.authorizationUrl;
+ console.log('saGetGoogleSSOAuthCode gets authorization url successfully');
+
+ const { width, height } = screen.getPrimaryDisplay().workAreaSize;
+
+ // Open the authorization URL in a new BrowserWindow
+ const authWindow = new BrowserWindow({
+ width: Math.floor(width * 0.8), // 80% of the screen width
+ height: Math.floor(height * 0.7), // 70% of the screen height
+ webPreferences: {
+ nodeIntegration: false,
+ contextIsolation: true,
+ enableRemoteModule: false,
+ },
+ });
+
+ authWindow.loadURL(authorizationUrl);
+
+ // Intercept the HTTP request to the callback URL
+ session.defaultSession.webRequest.onBeforeRequest(
+ { urls: ['http://localhost/callback*'] },
+ (details, callback) => {
+ const url = new URL(details.url);
+ const authCode = url.searchParams.get('code');
+
+ if (authCode) {
+ // Complete the request processing
+ callback({ cancel: false });
+
+ // Load a success message and close the auth window after the request processing is done
+ setImmediate(() => {
+ // a built-in function in Node.js that schedules a callback to be executed immediately after I/O events
+ authWindow.loadURL('data:text/html,Authentication successful! You can close this window.');
+ authWindow.close();
+
+ // Send the authorization code to the renderer process
+ mainWindow.webContents.send('saGoogleSSOAuthCodeReceived', authCode);
+ });
+ } else {
+ callback({ cancel: false });
+ }
+ }
+ );
+
+ return { login: true };
+ } else {
+ console.log('saGetGoogleSSOAuthCode failed to get authorization url: ', response);
+ return { login: false, error: response.error };
+ }
+ });
+
+ ipcMain.handle('saLoginUserGoogleSSO', async (event, args) => {
+ console.log('main: saLoginUserGoogleSSO');
+
+ const authCode = args.authCode;
+
+ if (!authCode) return { login: 'error', error: 'Missing required fields' };
+
+ const response = await acLoginUserGoogleSSO(authCode);
+
+ if (response.status === 'success') {
+ const cloudId = await msGetActiveCloud();
+ const regionName = await msGetActiveRegionName();
+ const cloudDescription = CloudInfo[cloudId].description;
+
+ const service = `${cloudDescription}/${regionName}`;
+ const theme = await msGetTheme();
+
+ return {
+ login: true,
+ user: { ...response.data, service, theme, cloudDescription, regionName },
+ };
+ }
+ });
+};
diff --git a/jccm/src/Services/ApiServer.js b/jccm/src/Services/ApiServer.js
index 4bcf6dd..4156e0f 100644
--- a/jccm/src/Services/ApiServer.js
+++ b/jccm/src/Services/ApiServer.js
@@ -45,7 +45,7 @@ import { CloudInfo } from '../config';
import { commitJunosSetConfig, executeJunosCommand, getDeviceFacts } from './Device';
const sshSessions = {};
-const serverGetCloudInventory = async () => {
+const serverGetCloudInventory = async (targetOrgs = null) => {
console.log('main: serverGetCloudInventory');
const orgFilters = await msGetOrgFilter();
@@ -56,19 +56,32 @@ const serverGetCloudInventory = async () => {
return { inventory: [], isFilterApplied: false };
}
+ const currentInventory = await msGetCloudInventory();
const inventory = [];
const orgs = {};
for (const v of selfData.data.privileges) {
if (v.scope === 'org') {
const orgId = v.org_id;
+ const orgName = v.name;
+
if (!!orgFilters[orgId]) continue;
- const item = { name: v.name, id: orgId };
+ if (targetOrgs !== null && !targetOrgs.includes(orgName)) {
+ // Find the organization in the current inventory with the same orgId
+ const existingOrg = currentInventory.find((org) => org.id === orgId);
+ if (existingOrg) {
+ // console.log('>>>> Reuse existing org:', JSON.stringify(existingOrg, null, 2));
+ inventory.push(existingOrg);
+ }
+ continue; // Skip further processing for this organization
+ }
+
+ const item = { name: orgName, id: orgId };
const sitesData = await acGetCloudSites(orgId);
if (sitesData.status === 'error') {
- console.error(`serverGetCloudInventory: acGetCloudSites error on org ${orgId}`)
+ console.error(`serverGetCloudInventory: acGetCloudSites error on org ${orgId}`);
continue;
}
@@ -76,12 +89,12 @@ const serverGetCloudInventory = async () => {
Object.entries(sitesData.data).map(([key, value]) => [value.name, { id: value.id }])
);
- orgs[v.name] = { id: v.org_id, sites };
-
+ orgs[orgName] = { id: orgId, sites };
if (sitesData.status === 'success') {
item.sites = sitesData.data;
}
+
const inventoryData = await acGetCloudInventory(orgId);
if (inventoryData.status === 'success') {
@@ -89,27 +102,27 @@ const serverGetCloudInventory = async () => {
const siteIdHavingVMAC = new Set();
try {
- for (const device of devices) {
- if (device.site_id) {
- for (const site of item['sites'] || []) {
- if (site.id === device.site_id) {
- device.site_name = site.name;
+ for (const device of devices) {
+ if (device.site_id) {
+ for (const site of item['sites'] || []) {
+ if (site.id === device.site_id) {
+ device.site_name = site.name;
+ }
}
}
+ device.org_name = item.name;
+ if (device?.mac.toUpperCase() === device.serial.toUpperCase() && device.type === 'switch') {
+ siteIdHavingVMAC.add(device.site_id);
+ }
}
- device.org_name = item.name;
- if (device?.mac.toUpperCase() === device.serial.toUpperCase() && device.type === 'switch') {
- siteIdHavingVMAC.add(device.site_id);
- }
+ } catch (error) {
+ console.error('serverGetCloudInventory: ', error);
}
- } catch (error) {
- console.error('serverGetCloudInventory: ', error);
- }
if (siteIdHavingVMAC.size > 0) {
const SN2VSN = {};
for (const siteId of siteIdHavingVMAC) {
- console.log(`Get device stats for site(${siteId})`)
+ console.log(`Get device stats for site(${siteId})`);
const response = await acGetDeviceStatsType(siteId, 'switch');
if (response.status === 'success') {
@@ -135,6 +148,7 @@ const serverGetCloudInventory = async () => {
item.inventory = devices;
}
+
inventory.push(item);
}
}
@@ -192,12 +206,9 @@ export const setupApiHandlers = () => {
const cloudDescription = CloudInfo[cloudId].description;
const service = `${cloudDescription}/${regionName}`;
const theme = await msGetTheme();
- const { inventory, isFilterApplied } = await serverGetCloudInventory();
return {
login: true,
user: { ...response.data, service, theme, cloudDescription, regionName },
- inventory,
- isFilterApplied,
};
}
}
@@ -209,13 +220,10 @@ export const setupApiHandlers = () => {
const cloudDescription = CloudInfo[cloudId].description;
const service = `${cloudDescription}/${regionName}`;
const theme = await msGetTheme();
- const { inventory, isFilterApplied } = await serverGetCloudInventory();
return {
login: true,
user: { ...response.data, service, theme, cloudDescription, regionName },
- inventory,
- isFilterApplied,
};
} else {
return { login: false, error: 'two factor auth failed' };
@@ -248,13 +256,13 @@ export const setupApiHandlers = () => {
const service = `${cloudDescription}/${regionName}`;
const theme = await msGetTheme();
- const { inventory, isFilterApplied } = await serverGetCloudInventory();
+ // const { inventory, isFilterApplied } = await serverGetCloudInventory();
return {
sessionValid: true,
user: { ...response.data, service, theme, cloudDescription, regionName },
- inventory,
- isFilterApplied,
+ // inventory,
+ // isFilterApplied,
};
} else {
const theme = await msGetTheme();
@@ -272,9 +280,11 @@ export const setupApiHandlers = () => {
await msSetTheme(theme);
});
- ipcMain.handle('saGetCloudInventory', async (event) => {
+ ipcMain.handle('saGetCloudInventory', async (event, args = {}) => {
console.log('main: saGetCloudInventory');
- const { inventory, isFilterApplied } = await serverGetCloudInventory();
+ const { targetOrgs = null } = args;
+
+ const { inventory, isFilterApplied } = await serverGetCloudInventory(targetOrgs);
return { cloudInventory: true, inventory, isFilterApplied };
});
@@ -638,13 +648,10 @@ export const setupApiHandlers = () => {
const service = `${cloudDescription}/${regionName}`;
const theme = await msGetTheme();
- const { inventory, isFilterApplied } = await serverGetCloudInventory();
return {
login: true,
user: { ...response.data, service, theme, cloudDescription, regionName },
- inventory,
- isFilterApplied,
};
}
});
diff --git a/jccm/src/preload.js b/jccm/src/preload.js
index b31f191..ea27e17 100644
--- a/jccm/src/preload.js
+++ b/jccm/src/preload.js
@@ -14,7 +14,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
saWhoamiUser: () => ipcRenderer.invoke('saWhoamiUser'),
saSetThemeUser: (args) => ipcRenderer.invoke('saSetThemeUser', args),
- saGetCloudInventory: () => ipcRenderer.invoke('saGetCloudInventory'),
+ saGetCloudInventory: (args) => ipcRenderer.invoke('saGetCloudInventory', args),
saProxyCall: (args) => ipcRenderer.invoke('saProxyCall', args),
saOrgFilter: (args) => ipcRenderer.invoke('saOrgFilter', args),
saGetLocalInventory: () => ipcRenderer.invoke('saGetLocalInventory'),