Skip to content

Commit cffd00b

Browse files
committed
[cisco-sg] add poe support
1 parent cc3e575 commit cffd00b

7 files changed

Lines changed: 301 additions & 43 deletions

File tree

src/modules/cisco-cbs/container/workers/worker-snmp.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ const main = async () => {
4343

4444
workerTaskManager({
4545
tasks: [
46-
{ name: "interfaces", seconds: 900 },
46+
{ name: "interfaces", seconds: 120 },
4747
{ name: "interfacedetails", seconds: 15, delay: 5 },
4848
{ name: "interfacestats", seconds: 10, delay: 8 },
4949
{ name: "interfacestate", seconds: 5, delay: 8 },

src/modules/cisco-sg/client/components/InterfaceList.jsx

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import BugApiVlanAutocomplete from "@core/BugApiVlanAutocomplete";
44
import BugAutocompletePlaceholder from "@core/BugAutocompletePlaceholder";
55
import BugNetworkIcon from "@core/BugNetworkIcon";
66
import BugNoData from "@core/BugNoData";
7+
import BugPoeIcon from "@core/BugPoeIcon";
78
import { useBugRenameDialog } from "@core/BugRenameDialog";
89
import BugSparkCell from "@core/BugSparkCell";
910
import BugTableLinkButton from "@core/BugTableLinkButton";
@@ -194,6 +195,20 @@ export default function InterfaceList({ panelId, stackId = null }) {
194195
return interfaceToggle(false, item);
195196
};
196197

198+
const handlePoeClicked = async (event, item) => {
199+
const action = item?.["poe-admin-enable"] ? "disable" : "enable";
200+
if (await AxiosCommand(`/container/${panelId}/interface/${action}poe/${item.interfaceId}`)) {
201+
doForceRefresh();
202+
sendAlert(`${action === "disable" ? "Disabled" : "Enabled"} POE for interface: ${item.shortId}`, {
203+
variant: "success",
204+
});
205+
} else {
206+
sendAlert(`Failed to ${action} POE for interface: ${item.shortId}`, {
207+
variant: "error",
208+
});
209+
}
210+
};
211+
197212
const handleProtectClicked = async (event, item) => {
198213
if (
199214
await AxiosCommand(
@@ -248,6 +263,22 @@ export default function InterfaceList({ panelId, stackId = null }) {
248263
width: 44,
249264
content: (item) => <BugNetworkIcon disabled={!item["link-state"]} />,
250265
},
266+
{
267+
noPadding: true,
268+
width: 44,
269+
content: (item) => {
270+
if (!item?.["poe-available"]) {
271+
return null;
272+
}
273+
return (
274+
<BugPoeIcon
275+
disabled={!item?.["poe-admin-enable"]}
276+
active={item?.["poe-operational-status"]}
277+
error={item?.["poe-error"]}
278+
/>
279+
);
280+
},
281+
},
251282
{
252283
noPadding: true,
253284
hideWidth: 600,
@@ -378,6 +409,12 @@ export default function InterfaceList({ panelId, stackId = null }) {
378409
{
379410
title: "-",
380411
},
412+
{
413+
title: "POE",
414+
disabled: (item) => !item?.["poe-available"],
415+
icon: (item) => (item?.["poe-admin-enable"] ? <CheckIcon fontSize="small" /> : null),
416+
onClick: handlePoeClicked,
417+
},
381418
{
382419
title: "Protect",
383420
disabled: (item) => item._protected && !item._allowunprotect,

src/modules/cisco-sg/container/api/routes/interface.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const interfaceUnprotect = require("@services/interface-unprotect");
1111
const interfaceSetVlanTrunk = require("@services/interface-setvlantrunk");
1212
const interfaceSetVlanAccess = require("@services/interface-setvlanaccess");
1313
const interfaceRename = require("@services/interface-rename");
14+
const interfacePoe = require("@services/interface-poe");
1415
const asyncHandler = require("express-async-handler");
1516

1617
router.all(
@@ -88,6 +89,26 @@ router.get(
8889
})
8990
);
9091

92+
router.get(
93+
"/enablepoe/:interfaceId/",
94+
asyncHandler(async (req, res) => {
95+
res.json({
96+
status: "success",
97+
data: await interfacePoe(req.params.interfaceId, "enable"),
98+
});
99+
})
100+
);
101+
102+
router.get(
103+
"/disablepoe/:interfaceId/",
104+
asyncHandler(async (req, res) => {
105+
res.json({
106+
status: "success",
107+
data: await interfacePoe(req.params.interfaceId, "disable"),
108+
});
109+
})
110+
);
111+
91112
router.post(
92113
"/setvlantrunk/:interfaceId/",
93114
asyncHandler(async (req, res) => {

src/modules/cisco-sg/container/api/routes/interface.test.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ jest.mock("@services/interface-unprotect", () => jest.fn(async () => ({ updated:
1212
jest.mock("@services/interface-setvlantrunk", () => jest.fn(async () => ({ updated: true })));
1313
jest.mock("@services/interface-setvlanaccess", () => jest.fn(async () => ({ updated: true })));
1414
jest.mock("@services/interface-rename", () => jest.fn(async () => ({ updated: true })));
15+
jest.mock("@services/interface-poe", () => jest.fn(async () => ({ updated: true })));
1516

1617
const interfaceRouter = require("./interface");
1718

@@ -37,4 +38,20 @@ describe("interface routes", () => {
3738
expect(response.body).toHaveProperty("status", "success");
3839
expect(response.body).toHaveProperty("data");
3940
});
41+
42+
test("GET /enablepoe/:interfaceId should return success response", async () => {
43+
const response = await request(app).get("/enablepoe/123/");
44+
45+
expect(response.statusCode).toBe(200);
46+
expect(response.body).toHaveProperty("status", "success");
47+
expect(response.body).toHaveProperty("data");
48+
});
49+
50+
test("GET /disablepoe/:interfaceId should return success response", async () => {
51+
const response = await request(app).get("/disablepoe/123/");
52+
53+
expect(response.statusCode).toBe(200);
54+
expect(response.body).toHaveProperty("status", "success");
55+
expect(response.body).toHaveProperty("data");
56+
});
4057
});
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
"use strict";
2+
3+
const SnmpAwait = require("@core/snmp-await");
4+
const configGet = require("@core/config-get");
5+
const mongoCollection = require("@core/mongo-collection");
6+
const deviceSetPending = require("@services/device-setpending");
7+
const logger = require("@core/logger")(module);
8+
9+
module.exports = async (interfaceId, action) => {
10+
let snmpAwait;
11+
12+
try {
13+
if (!interfaceId) {
14+
throw new Error("interfaceId is required");
15+
}
16+
17+
if (!["enable", "disable"].includes(action)) {
18+
throw new Error(`invalid action '${action}', must be 'enable' or 'disable'`);
19+
}
20+
21+
const config = await configGet();
22+
if (!config) {
23+
throw new Error("failed to load config");
24+
}
25+
26+
const actionText = action === "enable" ? "enabling" : "disabling";
27+
28+
snmpAwait = new SnmpAwait({
29+
host: config.address,
30+
community: config.snmpCommunity,
31+
});
32+
33+
logger.info(`${actionText} POE on interface ${interfaceId} ...`);
34+
35+
await snmpAwait.set({
36+
oid: `.1.3.6.1.2.1.105.1.1.1.3.1.${interfaceId}`,
37+
value: action === "enable" ? 1 : 2,
38+
});
39+
40+
logger.info("success - updating DB");
41+
42+
const interfacesCollection = await mongoCollection("interfaces");
43+
const dbResult = await interfacesCollection.updateOne(
44+
{ interfaceId: Number(interfaceId) },
45+
{ $set: { "poe-admin-enable": action === "enable" } }
46+
);
47+
48+
logger.info(`${JSON.stringify(dbResult.result)}`);
49+
50+
if (dbResult.matchedCount !== 1) {
51+
throw new Error(`expected to update 1 interface in DB, matched ${dbResult.matchedCount}`);
52+
}
53+
54+
await deviceSetPending(true);
55+
56+
logger.info("complete");
57+
} catch (err) {
58+
err.message = `${err.stack || err.message}`;
59+
logger.error(err.message);
60+
throw err;
61+
} finally {
62+
if (snmpAwait) {
63+
snmpAwait.close();
64+
}
65+
}
66+
};
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
const mockConfigGet = jest.fn();
2+
3+
jest.mock("@core/config-get", () => mockConfigGet);
4+
jest.mock("@core/snmp-await", () => jest.fn().mockImplementation(() => ({ set: jest.fn(), close: jest.fn() })));
5+
jest.mock("@core/mongo-collection", () => jest.fn());
6+
jest.mock("@services/device-setpending", () => jest.fn());
7+
jest.mock("@core/logger", () => () => ({ info: jest.fn(), error: jest.fn() }));
8+
9+
const interfacePoe = require("./interface-poe");
10+
11+
describe("interface-poe", () => {
12+
beforeEach(() => {
13+
mockConfigGet.mockReset();
14+
});
15+
16+
test("rejects when interfaceId is missing", async () => {
17+
await expect(interfacePoe(null, "enable")).rejects.toThrow("interfaceId is required");
18+
});
19+
20+
test("rejects when action is invalid", async () => {
21+
await expect(interfacePoe(1, "bad")).rejects.toThrow("invalid action");
22+
});
23+
24+
test("rejects when config is missing", async () => {
25+
mockConfigGet.mockResolvedValue(null);
26+
await expect(interfacePoe(1, "enable")).rejects.toThrow("failed to load config");
27+
});
28+
});

0 commit comments

Comments
 (0)