Overview
全球多个 office 网络,netSec,NST…集中管理门户
快速 实时 定位设备告警故障,性能异常,停机原因等
网络监控自动化,设备管理
Demo
技术栈
- Reactreact-router-dom
- Node Koa2Middleware 中间层
- jest enzyme单元测试
- rediskey-value 型轻量数据库,用于存储用户界面偏好
- material-uiUI 库
- lesscss 预处理
- eslintairbnb 标准
- axiospromis 库 前后端对接
- 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 的发布订阅告警

 
                     
                     
                 
                        
                        