Overview
全球多个 office 网络,netSec,NST…集中管理门户
快速 实时 定位设备告警故障,性能异常,停机原因等
网络监控自动化,设备管理
Demo
技术栈
React
react-router-domNode Koa2
Middleware 中间层jest enzyme
单元测试redis
key-value 型轻量数据库,用于存储用户界面偏好material-ui
UI 库less
css 预处理eslint
airbnb 标准axios
promis 库 前后端对接mockJs
拦截 Ajax 请求 生成随机数据umi
框架react-csv/moment/react-dates/react-hook-form
第三方库
Mark
History Router
在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面
// server app.js
const send = require("koa-send");
const path = require("path");
app.use(async (ctx, next) => {
if (ctx.status === 404 && ctx.body === undefined) {
await send(ctx, path.join(distDirectory, "index.html"));
}
await next();
});
这么做以后,node 方 不再返回 404 错误页面,因为对于所有路径都会返回 index.html 文件。
所以在客户端兜底 404,前端路由配置中加一个 defaultRoute,指向 404component{ path: '/404', component: '@/pages/404/index' },...,{ redirect: '/404' }
User Preference
node 层提供两个 api,一个 get,一个 set
// user-preference.js
const preferenceRouter = new Router();
preferenceRouter.get("/api/preference/", async (ctx, next) => {
try {
const {
user: { Corpid },
} = ctx.req;
if (Corpid) {
const res = await getUserPreference(Corpid);
ctx.body = { userPreference: res };
next();
}
} catch (error) {
next();
}
});
preferenceRouter.post("/api/preference/", async (ctx, next) => {
try {
const {
user: { Corpid },
} = ctx.req;
console.log(Corpid);
const { feature, pageName, value } = ctx.request.body;
if (Corpid && feature && value) {
const res = await setUserPreference({
user: Corpid,
feature,
pageName,
value,
});
if (res) ctx.status = 200;
next();
}
} catch (error) {
next();
}
});
module.exports = { preferenceRouter };
// app.js
const { preferenceRouter } = require("./user-preference");
app.use(preferenceRouter.routes());
其中 getUserPreference()和 setUserPreference() 操作 redis database,这里使用ioredis
as redis client
Redis.Command.setReplyTransformer("hgetall", (result) => {
if (Array.isArray(result)) {
const obj = {};
for (let i = 0; i < result.length; i += 2) {
obj[result[i]] = result[i + 1];
}
return obj;
}
return result;
});
const preferenceStore = new Redis({
...redisConfig,
keyPrefix: redisConfig.keyPrefix + "preference:",
});
async function getUserPreference(user) {
try {
const res = await preferenceStore.hgetall(user);
return res;
} catch (e) {
throw e;
}
}
async function setUserPreference({ user, feature, pageName, value }) {
try {
const res = await preferenceStore.hset(
user,
`${feature}_${pageName}`,
value
);
return true;
} catch (e) {
throw e;
}
}
module.exports = { getUserPreference, setUserPreference };
1: 前端本地应该维护一个全局变量,保存所有最新的 user-preference 值。在用户第一次打开 SPA 时向 node 获取 redis 数据库值;之后前端内部 每次变动 user-preference 时 都要维护这个全局变量(localStorage),hash-url 变动时,使用该全局变量 向 node 最新 redis(生命周期 mounted & unmount & window.onbeforeunload)。
2: 更新 redis 的时间点,可以选在前端每次改动 preference 时同时改变全局变量和向 node api 更新 redis 数据库。也可以在用户销毁 SPA 时向 node 更新 redis(相比前者 不推荐,因为,用户的所有改动都放在一个 api 里传风险大;而且降低每次的传参有助于加快每次 api 请求)
递归 Menu
const SideMenu = ({
currentMenuLevelArray,
defaultOpen = false,
badgetCountObj,
}) => {
return (
<List>
{currentMenuLevelArray.map((currentMenuLevelItem) => (
<SideMenuItem
key={currentMenuLevelItem.key}
currentMenuLevelItem={currentMenuLevelItem}
defaultOpen={defaultOpen}
badgetCountObj={badgetCountObj}
/>
))}
</List>
);
};
const SideMenuItem = ({
currentMenuLevelItem,
defaultOpen = false,
badgetCountObj,
}) => {
const { location } = useContext(appContext);
let pathnameArr = location.pathname.split("/");
document.title = `Lighthouse - ${
pathnameArr[pathnameArr.length - 1].slice(0, 1).toUpperCase() +
pathnameArr[pathnameArr.length - 1].slice(1)
}`;
const classes = useStyles();
const [open, setOpen] = React.useState(defaultOpen);
const LinkenListItem = LinkenListItemFn(
currentMenuLevelItem.children ? true : false
);
return (
<React.Fragment>
<LinkenListItem
button
currentMenuLevelItem={currentMenuLevelItem}
open={open}
clickFn={() => setOpen(!open)}
>
{currentMenuLevelItem.icon && (
<ListItemIcon>
{React.createElement(currentMenuLevelItem.icon)}
</ListItemIcon>
)}
<Badge
badgeContent={badgetCountObj[currentMenuLevelItem.key] || 0}
color="primary"
max={999}
// anchorOrigin={{
// horizontal: 'left',
// vertical: 'top',
// }}
>
<ListItemText
primary={currentMenuLevelItem.title}
className={
currentMenuLevelItem.key === location.pathname
? classes.primaryColor
: null
}
/>
</Badge>
</LinkenListItem>
{currentMenuLevelItem.children && (
<Collapse
in={open}
timeout="auto"
unmountOnExit
className={classes.nested}
>
<SideMenu
currentMenuLevelArray={currentMenuLevelItem.children}
badgetCountObj={badgetCountObj}
/>
</Collapse>
)}
</React.Fragment>
);
};
位运算
涉及到 ip 范围(掩码)的 validation,如 10.23.0.0/20 代表前 20 位固定,后 14 位不限,所以前面的 ip 格式需满足在二进制中后 14 位为 0;
AND 位运算 取整,>>> 0
去符号(右移 0)
const ipInt = require("ip-to-int");
const [rawIp, bit = 32] = rawNetwork.split("/");
const RawIntIp = ipInt(rawIp).toInt();
const strictIp = ipInt(
`${(RawIntIp & (2 ** 32 - 2 ** (32 - bit))) >>> 0}`
).toIP();
let result = [[strictIp, bit.toString()].join("/")];
back reminder
netBrain Qapps
通过定制 actions,实现智能 track alert causezabbix
来监控网络 devices 的告警 alerts,应用 SNMP(Simple Network Management Protocol),定时间隔去主动向设备 poll metric or datasettelemetry vs snmp
. 个别设备内支持telemetry
协议,设备主动 live update 的发布订阅告警