[
  {
    "path": ".github/workflows/release.yml",
    "content": "name: 编译并发布项目\non:\n  push:\n    tags:\n      - 'v*'\njobs:\n  release:\n    name: \"编译发布release程序\"\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        node-version: [18.x]\n        go-version: [1.19.x]\n    steps:\n      - uses: actions/checkout@v3\n      - name: Use Node.js ${{ matrix.node-version }}\n        uses: actions/setup-node@v3\n        with:\n          node-version: ${{ matrix.node-version }}\n      - name: Use Go ${{ matrix.go-version }}\n        uses: actions/setup-go@v3\n        with:\n          go-version: ${{ matrix.go-version }}\n      - name: Build moyu-manager frontend\n        working-directory: ./manager/frontend\n        run: |\n          npm install\n          npm run build\n      - name: Build moyu-manager release binary\n        working-directory: ./manager/backend\n        run: |\n          go get .\n          GOOS=linux GOARCH=amd64 go build --ldflags=\"-w -s\" -o moyu-manager-linux-amd64 .\n          GOOS=linux GOARCH=arm64 go build --ldflags=\"-w -s\" -o moyu-manager-linux-arm64 .\n          zip -r ../../moyu-manager-linux-amd64.zip moyu-manager-linux-amd64\n          zip -r ../../moyu-manager-linux-arm64.zip moyu-manager-linux-arm64\n          zip -r ../../init.sql.zip database/init.sql\n      - name: Build moyu-page frontend\n        working-directory: ./page/frontend\n        run: |\n          npm install\n          npm run build\n      - name: Build moyu-page release binary\n        working-directory: ./page/backend\n        run: |\n          go get .\n          GOOS=linux GOARCH=amd64 go build --ldflags=\"-w -s\" -o moyu-page-linux-amd64 .\n          GOOS=linux GOARCH=arm64 go build --ldflags=\"-w -s\" -o moyu-page-linux-arm64 .\n          zip -r ../../moyu-page-linux-amd64.zip moyu-page-linux-amd64\n          zip -r ../../moyu-page-linux-arm64.zip moyu-page-linux-arm64\n      - uses: \"marvinpinto/action-automatic-releases@latest\"\n        with:\n          repo_token: \"${{ secrets.GITHUB_TOKEN }}\"\n          prerelease: false\n          files: |\n            *.zip\n            "
  },
  {
    "path": ".gitignore",
    "content": "target/"
  },
  {
    "path": "Makefile",
    "content": "all: manager page\n\tmkdir -p target/bin && mkdir -p target/db && mv manager/backend/moyu-manager target/bin/ && mv page/backend/moyu-page target/bin/ && cat manager/backend/database/init.sql | sqlite3 target/db/moyu_manager.db\n\nmanager: manager-frontend\n\tcd manager/backend && go build -o moyu-manager --ldflags=\"-w -s\" .\n\nmanager-frontend:\n\tcd manager/frontend && npm install && npm run build\n\npage: page-frontend\n\tcd page/backend && go build -o moyu-page --ldflags=\"-w -s\" .\n\npage-frontend:\n\tcd page/frontend && npm install && npm run build\n\nclean:\n\t-rm -rf page/backend/dist page/backend/moyu-page manager/backend/dist manager/backend/moyu-manager target\n\n.PHONY: page-frontend page manager-frontend manager clean\n"
  },
  {
    "path": "ManagerDockerfile",
    "content": "FROM alpine:3.17.1 as builder\nCOPY . .\nRUN apk add go nodejs npm\nRUN cd manager/frontend && npm install && npm run build && \\\n    cd ../backend && go get . && go build -o /tmp/moyu-manager --ldflags=\"-w -s\" .\n\nFROM alpine:3.17.1 as deploy\nEXPOSE 8080\nRUN mkdir -p /moyu-manager/bin && mkdir -p /moyu-manager/db\nCOPY --from=builder /tmp/moyu-manager /moyu-manager/bin/\nENTRYPOINT /moyu-manager/bin/moyu-manager"
  },
  {
    "path": "PageDockerfile",
    "content": "FROM alpine:3.17.1 as builder\nCOPY . .\nRUN apk add go nodejs npm\nRUN cd page/frontend && npm install && npm run build && \\\n    cd ../backend && go get . && go build -o moyu-page --ldflags=\"-w -s\" .\n\nFROM alpine:3.17.1 as deploy\nEXPOSE 8081\nCOPY --from=builder backend/moyu-page .\nENTRYPOINT /moyu-page"
  },
  {
    "path": "README.md",
    "content": "# 墨鱼探针\n\n## 简介\n\n墨鱼探针为主从模式，主为`墨鱼manager`，从为`墨鱼page`。`墨鱼manager`可获取展示多个墨鱼page的简要状态信息，登录后可点击title进入墨鱼page页面。`墨鱼page`独立部署，可独立使用。\n\n技术栈：go(fiber) + vue2 + element-ui + nes.css。\n\n最终将前端和后端全部编译到单个二进制程序中，分为`moyu-manager`和`moyu-page`2个二进制程序。\n\n## 展示\n\n### 墨鱼page\n\n![moyu_page](./imgs/moyu_page.png)\n\n### 墨鱼manager\n\n未登录主页：\n\n![moyu_manager_main_unlogin](./imgs/moyu_manager_main_unlogin.png)\n\n已登录主页：\n\n![moyu_manager_main_login](./imgs/moyu_manager_main_login.png)\n\n登录页：\n\n![moyu_manager_login](./imgs/moyu_manager_login.png)\n\n添加墨鱼page页面：\n\n![moyu_manager_add_page](./imgs/moyu_manager_add_page.png)\n\n## 直接使用release文件\n\n### moyu-page\n\n1. 直接运行\n    ```bash\n    PORT=8081 ./moyu-page\n    ```\n\n### moyu-manager\n\n1. 初始化数据库(第一次运行)\n    ```bash\n    cat init.sql | sqlite3 db/moyu_manager.db\n    ```\n2. 目录结构\n    ```bash\n    ├── bin\n    │   └── moyu-manager\n    └── db\n        └── moyu_manager.db\n    ```\n3. 运行\n    ```bash\n    PORT=8080 ./bin/moyu-manager\n    ```\n\n4. 添加墨鱼page。如本项目在线demo：`https://moyu-manager.linux.plus/`，直接复制墨鱼page主页`https://moyu-page.linux.plus/`到`添加墨鱼page页面`即可。在墨鱼manager中，如果已登录，可以点击title访问墨鱼page。\n\n## 编译使用\n\n### 依赖：\n\nmake: ^4.0\n\nnodejs: ^18.0\n\ngo: ^1.19.0\n\nsqlite: ^3.0\n\n### 一键编译\n\n1. 进入项目目录。\n    ```bash\n    cd moyu\n    ```\n2. 一键编译。\n    ```bash\n    make\n    ```\n3. 运行墨鱼manager和墨鱼page。\n    ```bash\n    PORT=8081 target/bin/moyu-page\n    PORT=8080 target/bin/moyu-manager\n    ```\n4. （可选）清理项目，删除编译的墨鱼探针二进制等文件。\n    ```bash\n    make clean\n    ```\n\n### 手动编译\n\n#### 编译墨鱼page\n\n1. 进入项目目录。\n    ```bash\n    cd moyu/page\n    ```\n2. 编译前端资源。\n    ```bash\n    cd frontend\n    npm run build\n    ```\n3. 编译后端项目。\n    ```bash\n    cd ../backend\n    go build -o moyu-page --ldflags=\"-w -s\" .\n    ```\n\n#### 编译墨鱼manager\n\n1. 进入项目目录。\n    ```bash\n    cd moyu/manager\n    ```\n2. 编译前端资源。\n    ```bash\n    cd frontend\n    npm run build\n    ```\n3. 编译后端项目。\n    ```bash\n    cd ../backend\n    go build -o moyu-manager --ldflags=\"-w -s\" .\n    ```\n\n## Docker运行\n\n### 前提说明\n\n#### 墨鱼page\n\n墨鱼page直接运行docker无法获取宿主机信息，需要添加一些运行参数：\n\n1. 由于需要获取宿主机网络接口流量，所以需要以host方式运行docker。\n2. 获取磁盘分区信息需要`/proc/N/mountinfo`，所以需要将宿主机的某个进程的文件挂载到docker中，然后设置`HOST_PROC_MOUNTINFO`并运行项目。\n3. 项目`PORT`变量默认`8081`，可自行指定其他端口。\n\n#### 墨鱼manager\n\n1. 墨鱼manager依赖`sqlite3`，需要使用`moyu/manager/backend/database/init.sql`创建和初始化用户表数据。\n2. 请使用一下目录格式存放`moyu-manager`程序和数据库，容器挂载时需要注意目录位置，默认编译的项目根目录为`/moyu-manager`。\n    ```bash\n    root@liuxu:/moyu-manager# tree\n    .\n    ├── bin\n    │   └── moyu-manager\n    └── db\n        └── moyu_manager.db\n    ```\n\n### 运行容器\n\n#### 墨鱼page\n\n1. 编译page镜像。\n    ```bash\n    docker build -t moyu-page -f PageDockerfile .\n    ```\n2. 单磁盘挂载情况下运行，其中`--network=host`指定使用宿主机网络，`--mount`挂载`dockerd`的进程`mountinfo`文件到docker中，并设置`HOST_PROC_MOUNTINFO`为挂载的文件路径。\n    ```bash\n    docker run --network=host -e PORT=8081 --mount type=bind,source=\"/proc/$(pidof dockerd)/mountinfo\",target=/root/mountinfo -e HOST_PROC_MOUNTINFO=/root/mountinfo moyu-page\n    ```\n3. （可选）如果还有其他分区，如我的`/boot/efi`挂载到了独立分区，想获取到这个分区信息，需要把这个目录挂载到docker中。\n    ```bash\n    docker run --network=host -e PORT=8081 -v /boot/efi:/boot/efi:ro --mount type=bind,source=\"/proc/$(pidof dockerd)/mountinfo\",target=/root/mountinfo -e HOST_PROC_MOUNTINFO=/root/mountinfo moyu-page\n    ```\n\n#### 墨鱼manager\n\n1. 编译manager镜像。\n    ```bash\n    docker build -t moyu-manager -f ManagerDockerfile .\n    ```\n2. 创建数据库。\n    ```bash\n    mkdir db\n    cat manager/backend/database/init.sql | sqlite3 db/moyu_manager.db\n    ```\n3. 挂载数据库运行。\n    ```bash\n    docker run -e PORT=8080 -p 8080:8080 -v ./db:/moyu-manager/db  moyu-manager\n    ```\n\n## FAQ\n\n1. 我使用nginx做反代，设置了`location /moyu{}`该怎么办\n\n    答：运行墨鱼page或墨鱼manager时，添加`BASEURL=/moyu`环境变量运行。\n\n2. 运行墨鱼manager，程序报找不到`moyu_manager.db`怎么办\n\n    答：确认自己的目录结构是不是如下：\n    ```bash\n    ├── bin\n    │   └── moyu-manager\n    └── db\n        └── moyu_manager.db\n    ```\n    然后进入`bin/`目录下运行`moyu-manager`\n\n3. 我想修改账号或者token\n\n    答：运行sqlite3，通过sql修改。\n    ```bash\n    sqlite3 moyu_manager.db\n    sqlite> UPDATE users SET token='token123' WHERE user='user'\n    ```\n\n4. 我想创建新的账户和token\n\n    答：运行sqlite3，通过sql添加。\n    ```bash\n    sqlite3 moyu_manager.db\n    sqlite> INSERT INTO users(user, token) VALUES('user1', 'token1');\n    ```\n"
  },
  {
    "path": "manager/backend/.gitignore",
    "content": "dist/\nmoyu-manager"
  },
  {
    "path": "manager/backend/controller/page_data_controller/page_data_controller.go",
    "content": "package page_data_controller\n\nimport (\n\t\"encoding/json\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/gofiber/websocket/v2\"\n\t\"github.com/liuquanhao/moyu/model\"\n\t\"github.com/liuquanhao/moyu/model/remote_url_model\"\n)\n\n// define: page/backend/service/page_data.go\ntype PageData struct {\n\tUptime        uint64  `json:\"uptime\"`\n\tCPUPercent    float64 `json:\"cpu_percent\"`\n\tMemoryPercent float64 `json:\"memory_percent\"`\n\tDiskPercent   float64 `json:\"disk_percent\"`\n\tNetSendTotal  uint64  `json:\"net_send\"`\n\tNetRecvTotal  uint64  `json:\"net_recv\"`\n\tTimestamp     int64   `json:\"timestamp\"`\n}\n\ntype PageInfo struct {\n\tPageId   uint32    `json:\"page_id\"`\n\tPageData *PageData `json:\"page_data\"`\n}\n\nfunc getPageData(myClient *http.Client, url string, target interface{}) error {\n\tresp, err := http.Get(url + \"api/page_data\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tif resp.StatusCode != 200 {\n\t\treturn err\n\t}\n\tdefer resp.Body.Close()\n\treturn json.NewDecoder(resp.Body).Decode(target)\n}\n\nfunc loopPullPageData(pageId uint32, url string, ch chan *PageInfo) {\n\tvar myClient = &http.Client{Timeout: 5 * time.Second}\n\tpageData := new(PageData)\n\tfor {\n\t\tstart := time.Now()\n\t\terr := getPageData(myClient, url, pageData)\n\t\tif err != nil {\n\t\t\ttime.Sleep(3 * time.Second)\n\t\t} else {\n\t\t\tch <- &PageInfo{\n\t\t\t\tPageId:   pageId,\n\t\t\t\tPageData: pageData,\n\t\t\t}\n\t\t}\n\t\tend := time.Now()\n\t\tuseTime := end.Sub(start)\n\t\tif useTime < 1*time.Second {\n\t\t\ttime.Sleep(1000*time.Millisecond - useTime)\n\t\t}\n\t}\n}\n\nfunc PushPageData(c *websocket.Conn) {\n\tdb := model.DBConn\n\tvar pageUrls []remote_url_model.PageUrl\n\tdb.Find(&pageUrls)\n\tch := make(chan *PageInfo, len(pageUrls))\n\tdefer close(ch)\n\tfor _, pageUrl := range pageUrls {\n\t\tgo loopPullPageData(pageUrl.Id, pageUrl.PageUrl, ch)\n\t}\n\tfor pageInfo := range ch {\n\t\tc.WriteJSON(pageInfo)\n\t}\n}\n"
  },
  {
    "path": "manager/backend/controller/page_url_controller/page_url_controller.go",
    "content": "package page_url_controller\n\nimport (\n\t\"github.com/gofiber/fiber/v2\"\n\t\"github.com/liuquanhao/moyu/model\"\n\t\"github.com/liuquanhao/moyu/model/remote_url_model\"\n\n\t\"github.com/go-playground/validator/v10\"\n)\n\nvar validate = validator.New()\n\nfunc Add(c *fiber.Ctx) error {\n\tdb := model.DBConn\n\tpageUrl := new(remote_url_model.PageUrl)\n\tif err := c.BodyParser(pageUrl); err != nil {\n\t\treturn c.Status(fiber.StatusBadRequest).SendString(err.Error())\n\t}\n\tif err := validate.Struct(pageUrl); err != nil {\n\t\treturn c.Status(fiber.StatusBadRequest).SendString(err.Error())\n\t}\n\tdb.Create(&pageUrl)\n\treturn c.JSON(pageUrl)\n}\n\nfunc Delete(c *fiber.Ctx) error {\n\tid := c.Params(\"id\")\n\tdb := model.DBConn\n\tvar pageUrl remote_url_model.PageUrl\n\tdb.Take(&pageUrl, id)\n\tif pageUrl.Title == \"\" {\n\t\treturn c.Status(fiber.StatusNotFound).SendString(\"Not Found\")\n\t}\n\tdb.Delete(&pageUrl)\n\treturn c.SendString(\"Delete success\")\n}\n\nfunc List(c *fiber.Ctx) error {\n\tdb := model.DBConn\n\tvar pageUrls []remote_url_model.PageUrl\n\tdb.Find(&pageUrls)\n\ts, _ := model.SessionStore.Get(c)\n\tif !s.Fresh() {\n\t\treturn c.JSON(pageUrls)\n\t}\n\t// unlogin\n\tfor _, pageUrl := range pageUrls {\n\t\tpageUrl.PageUrl = \"\"\n\t}\n\treturn c.JSON(pageUrls)\n}\n"
  },
  {
    "path": "manager/backend/controller/user_controller/user_controller.go",
    "content": "package user_controller\n\nimport (\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/go-playground/validator/v10\"\n\t\"github.com/gofiber/fiber/v2\"\n\t\"github.com/liuquanhao/moyu/model\"\n\t\"github.com/liuquanhao/moyu/model/user_model\"\n)\n\nvar validate = validator.New()\n\nfunc Login(c *fiber.Ctx) error {\n\tdb := model.DBConn\n\tuser := new(user_model.User)\n\tif err := c.BodyParser(user); err != nil {\n\t\treturn c.Status(fiber.StatusBadRequest).SendString(err.Error())\n\t}\n\tif err := validate.Struct(user); err != nil {\n\t\treturn c.Status(fiber.StatusBadRequest).SendString(err.Error())\n\t}\n\tdb.Where(&user).Take(&user)\n\tif user.Id == 0 {\n\t\treturn c.Status(fiber.StatusUnauthorized).SendString(\"Not Found User\")\n\t}\n\ts, _ := model.SessionStore.Get(c)\n\ts.Set(\"uid\", user.Id)\n\ts.Set(\"sid\", s.ID())\n\ts.Set(\"ip\", c.Context().RemoteIP().String())\n\ts.Set(\"login\", time.Unix(time.Now().Unix(), 0).UTC().String())\n\ts.Set(\"ua\", string(c.Request().Header.UserAgent()))\n\terr := s.Save()\n\tif err != nil {\n\t\treturn c.Status(fiber.StatusServiceUnavailable).SendString(err.Error())\n\t}\n\tcookie := new(fiber.Cookie)\n\tcookie.Name = \"uid\"\n\tcookie.Value = strconv.FormatUint(uint64(user.Id), 10)\n\tcookie.Expires = time.Now().Add(24 * time.Hour)\n\tc.Cookie(cookie)\n\tc.Status(fiber.StatusNoContent)\n\treturn nil\n}\n\nfunc Account(c *fiber.Ctx) error {\n\ts, _ := model.SessionStore.Get(c)\n\tif s.Fresh() {\n\t\tc.Status(fiber.StatusNotFound)\n\t\treturn nil\n\t}\n\treturn c.JSON(fiber.Map{\n\t\t\"sid\":   s.ID(),\n\t\t\"uid\":   s.Get(\"uid\"),\n\t\t\"ip\":    s.Get(\"ip\"),\n\t\t\"login\": s.Get(\"login\"),\n\t\t\"ua\":    s.Get(\"ua\"),\n\t})\n}\n\nfunc Logout(c *fiber.Ctx) error {\n\ts, _ := model.SessionStore.Get(c)\n\tif err := s.Destroy(); err != nil {\n\t\treturn c.Status(fiber.StatusServiceUnavailable).SendString(err.Error())\n\t}\n\tc.Status(fiber.StatusNoContent)\n\treturn nil\n}\n"
  },
  {
    "path": "manager/backend/database/init.sql",
    "content": "CREATE TABLE IF NOT EXISTS sessions (\n    k  VARCHAR(64) PRIMARY KEY NOT NULL DEFAULT '',\n    v  BLOB NOT NULL,\n    e  BIGINT NOT NULL DEFAULT '0',\n    u  TEXT\n);\nCREATE TABLE IF NOT EXISTS users (\n    id INTEGER PRIMARY KEY AUTOINCREMENT,\n    user VARCHAR(16) UNIQUE NOT NULL,\n    token VARCHAR(32) NOT NULL\n);\nCREATE TABLE IF NOT EXISTS page_urls (\n    id INTEGER PRIMARY KEY AUTOINCREMENT,\n    title VARCHAR(32) NOT NULL,\n    page_url VARCHAR(512) NOT NULL\n);\n\nCREATE UNIQUE INDEX user_uiq on users (user);\nINSERT INTO users(user, token) VALUES (\"user\", \"token123\");"
  },
  {
    "path": "manager/backend/go.mod",
    "content": "module github.com/liuquanhao/moyu\n\ngo 1.19\n\nrequire (\n\tgithub.com/go-playground/validator/v10 v10.11.2\n\tgithub.com/gofiber/fiber/v2 v2.42.0\n\tgithub.com/gofiber/storage/sqlite3 v0.0.0-20230206084615-41a84b36b572\n\tgithub.com/gofiber/websocket/v2 v2.1.4\n\tgorm.io/driver/sqlite v1.4.4\n\tgorm.io/gorm v1.24.5\n)\n\nrequire (\n\tgithub.com/andybalholm/brotli v1.0.4 // indirect\n\tgithub.com/fasthttp/websocket v1.5.1 // indirect\n\tgithub.com/go-playground/locales v0.14.1 // indirect\n\tgithub.com/go-playground/universal-translator v0.18.1 // indirect\n\tgithub.com/google/uuid v1.3.0 // indirect\n\tgithub.com/jinzhu/inflection v1.0.0 // indirect\n\tgithub.com/jinzhu/now v1.1.5 // indirect\n\tgithub.com/klauspost/compress v1.15.9 // indirect\n\tgithub.com/leodido/go-urn v1.2.1 // indirect\n\tgithub.com/mattn/go-colorable v0.1.13 // indirect\n\tgithub.com/mattn/go-isatty v0.0.17 // indirect\n\tgithub.com/mattn/go-runewidth v0.0.14 // indirect\n\tgithub.com/mattn/go-sqlite3 v1.14.16 // indirect\n\tgithub.com/philhofer/fwd v1.1.1 // indirect\n\tgithub.com/rivo/uniseg v0.2.0 // indirect\n\tgithub.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94 // indirect\n\tgithub.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d // indirect\n\tgithub.com/tinylib/msgp v1.1.6 // indirect\n\tgithub.com/valyala/bytebufferpool v1.0.0 // indirect\n\tgithub.com/valyala/fasthttp v1.44.0 // indirect\n\tgithub.com/valyala/tcplisten v1.0.0 // indirect\n\tgolang.org/x/crypto v0.5.0 // indirect\n\tgolang.org/x/sys v0.4.0 // indirect\n\tgolang.org/x/text v0.6.0 // indirect\n)\n"
  },
  {
    "path": "manager/backend/go.sum",
    "content": "github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=\ngithub.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/fasthttp/websocket v1.5.1 h1:iZsMv5OtZ1E52hhCnlOm/feLCrPhutlrZgvEGcZa1FM=\ngithub.com/fasthttp/websocket v1.5.1/go.mod h1:s+gJkEn38QXLkNfOe/n75Yb8we+VEho1vYqeUYheomw=\ngithub.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=\ngithub.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=\ngithub.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=\ngithub.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=\ngithub.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=\ngithub.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU=\ngithub.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s=\ngithub.com/gofiber/fiber/v2 v2.42.0 h1:Fnp7ybWvS+sjNQsFvkhf4G8OhXswvB6Vee8hM/LyS+8=\ngithub.com/gofiber/fiber/v2 v2.42.0/go.mod h1:3+SGNjqMh5VQH5Vz2Wdi43zTIV16ktlFd3x3R6O1Zlc=\ngithub.com/gofiber/storage/sqlite3 v0.0.0-20230206084615-41a84b36b572 h1:FHnNfM4q4Z+WtuYoQ5vz2z+JWmsY/sV49IS1nekEko4=\ngithub.com/gofiber/storage/sqlite3 v0.0.0-20230206084615-41a84b36b572/go.mod h1:TXYMAnzU/KAYE7RrM5tH9FrFC9Sr0CgMySWpMedmJR8=\ngithub.com/gofiber/utils v1.0.1 h1:knct4cXwBipWQqFrOy1Pv6UcgPM+EXo9jDgc66V1Qio=\ngithub.com/gofiber/utils v1.0.1/go.mod h1:pacRFtghAE3UoknMOUiXh2Io/nLWSUHtQCi/3QASsOc=\ngithub.com/gofiber/websocket/v2 v2.1.4 h1:Ki6L7auleAwgi7iRmtUiWKltlbmtkCJ0COtK1nt8L3g=\ngithub.com/gofiber/websocket/v2 v2.1.4/go.mod h1:IC4ZUejlk0kJSaphJ1gjqgKfK9fhw8eoAr3/UdbOzEA=\ngithub.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=\ngithub.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=\ngithub.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=\ngithub.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=\ngithub.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=\ngithub.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=\ngithub.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY=\ngithub.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=\ngithub.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=\ngithub.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=\ngithub.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=\ngithub.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=\ngithub.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=\ngithub.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=\ngithub.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=\ngithub.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=\ngithub.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=\ngithub.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=\ngithub.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=\ngithub.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=\ngithub.com/philhofer/fwd v1.1.1 h1:GdGcTjf5RNAxwS4QLsiMzJYj5KEvPJD3Abr261yRQXQ=\ngithub.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=\ngithub.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=\ngithub.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94 h1:rmMl4fXJhKMNWl+K+r/fq4FbbKI+Ia2m9hYBLm2h4G4=\ngithub.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94/go.mod h1:90zrgN3D/WJsDd1iXHT96alCoN2KJo6/4x1DZC3wZs8=\ngithub.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d h1:Q+gqLBOPkFGHyCJxXMRqtUgUbTjI8/Ze8vu8GGyNFwo=\ngithub.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d/go.mod h1:Gy+0tqhJvgGlqnTF8CVGP0AaGRjwBtXs/a5PA0Y3+A4=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=\ngithub.com/tinylib/msgp v1.1.6 h1:i+SbKraHhnrf9M5MYmvQhFnbLhAXSDWF8WWsuyRdocw=\ngithub.com/tinylib/msgp v1.1.6/go.mod h1:75BAfg2hauQhs3qedfdDZmWAPcFMAvJE5b9rGOMufyw=\ngithub.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=\ngithub.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=\ngithub.com/valyala/fasthttp v1.44.0 h1:R+gLUhldIsfg1HokMuQjdQ5bh9nuXHPIfvkYUu9eR5Q=\ngithub.com/valyala/fasthttp v1.44.0/go.mod h1:f6VbjjoI3z1NDOZOv17o6RvtRSWxC77seBFc2uWtgiY=\ngithub.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=\ngithub.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=\ngithub.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=\ngolang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=\ngolang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=\ngolang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=\ngolang.org/x/net v0.0.0-20220906165146-f3363e06e74c/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=\ngolang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=\ngolang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=\ngolang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngorm.io/driver/sqlite v1.4.4 h1:gIufGoR0dQzjkyqDyYSCvsYR6fba1Gw5YKDqKeChxFc=\ngorm.io/driver/sqlite v1.4.4/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI=\ngorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=\ngorm.io/gorm v1.24.5 h1:g6OPREKqqlWq4kh/3MCQbZKImeB9e6Xgc4zD+JgNZGE=\ngorm.io/gorm v1.24.5/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=\n"
  },
  {
    "path": "manager/backend/main.go",
    "content": "package main\n\nimport (\n\t\"embed\"\n\t\"io/fs\"\n\t\"log\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"time\"\n\n\t\"github.com/liuquanhao/moyu/controller/page_data_controller\"\n\t\"github.com/liuquanhao/moyu/controller/page_url_controller\"\n\t\"github.com/liuquanhao/moyu/controller/user_controller\"\n\t\"github.com/liuquanhao/moyu/middleware\"\n\t\"github.com/liuquanhao/moyu/model\"\n\n\t\"github.com/gofiber/fiber/v2\"\n\t\"github.com/gofiber/fiber/v2/middleware/cors\"\n\t\"github.com/gofiber/fiber/v2/middleware/filesystem\"\n\t\"github.com/gofiber/fiber/v2/middleware/session\"\n\t\"github.com/gofiber/storage/sqlite3\"\n\t\"github.com/gofiber/websocket/v2\"\n\t\"gorm.io/driver/sqlite\"\n\t\"gorm.io/gorm\"\n)\n\nfunc getCurrentPwd() string {\n\texePath, _ := os.Executable()\n\treturn filepath.Dir(exePath)\n}\nfunc initDatabase() {\n\tvar err error\n\tmodel.DBConn, err = gorm.Open(sqlite.Open(getCurrentPwd() + \"/../db/moyu_manager.db\"))\n\tif err != nil {\n\t\tlog.Fatal(\"failed to connect database: {}\", err)\n\t}\n}\n\nfunc initSession() {\n\tstorage := sqlite3.New(sqlite3.Config{\n\t\tDatabase:        getCurrentPwd() + \"/../db/moyu_manager.db\",\n\t\tTable:           \"sessions\",\n\t\tReset:           false,\n\t\tGCInterval:      10 * time.Second,\n\t\tMaxOpenConns:    100,\n\t\tMaxIdleConns:    100,\n\t\tConnMaxLifetime: 1 * time.Second,\n\t})\n\n\tmodel.SessionStore = session.New(session.Config{\n\t\tStorage: storage,\n\t})\n}\n\nfunc setupRoutes(app *fiber.App) {\n\tapp.Use(cors.New(cors.Config{\n\t\tAllowOrigins:     \"*\",\n\t\tAllowCredentials: true,\n\t}))\n\tbase := app.Group(os.Getenv(\"BASEURL\"))\n\n\tws := base.Group(\"/ws\")\n\tws.Use(\"/*\", middleware.UpgradeOptions)\n\tws.Get(\"/page_data\", websocket.New(page_data_controller.PushPageData))\n\n\tapi := base.Group(\"/api\")\n\tapi.Post(\"/login\", user_controller.Login)\n\tapi.Get(\"/page_url\", page_url_controller.List)\n\n\tlogged := app.Group(\"/api\", middleware.Logged)\n\tlogged.Post(\"/logout\", user_controller.Logout)\n\tlogged.Get(\"/account\", user_controller.Account)\n\tlogged.Post(\"/page_url\", page_url_controller.Add)\n\tlogged.Delete(\"/page_url/:id\", page_url_controller.Delete)\n\n\tstripped, err := fs.Sub(frontend, \"dist\")\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tbase.Use(\"/\", filesystem.New(filesystem.Config{\n\t\tRoot:   http.FS(stripped),\n\t\tBrowse: true,\n\t}))\n}\n\n//go:embed dist\nvar frontend embed.FS\n\nfunc main() {\n\tapp := fiber.New()\n\n\tinitDatabase()\n\tinitSession()\n\tsetupRoutes(app)\n\n\thost := os.Getenv(\"HOST\")\n\tport := os.Getenv(\"PORT\")\n\tlog.Println(host + \":\" + port)\n\tlog.Fatal(app.Listen(host + \":\" + port))\n}\n"
  },
  {
    "path": "manager/backend/middleware/logged.go",
    "content": "package middleware\n\nimport (\n\t\"github.com/gofiber/fiber/v2\"\n\t\"github.com/liuquanhao/moyu/model\"\n)\n\nfunc Logged(c *fiber.Ctx) error {\n\n\ts, _ := model.SessionStore.Get(c)\n\tif s.Fresh() {\n\t\tc.Status(fiber.StatusUnauthorized)\n\t\treturn nil\n\t}\n\treturn c.Next()\n}\n"
  },
  {
    "path": "manager/backend/middleware/ws_options.go",
    "content": "package middleware\n\nimport (\n\t\"github.com/gofiber/fiber/v2\"\n\t\"github.com/gofiber/websocket/v2\"\n)\n\nfunc UpgradeOptions(c *fiber.Ctx) error {\n\tif websocket.IsWebSocketUpgrade(c) {\n\t\treturn c.Next()\n\t}\n\treturn fiber.ErrUpgradeRequired\n}\n"
  },
  {
    "path": "manager/backend/model/db.go",
    "content": "package model\n\nimport \"gorm.io/gorm\"\n\nvar (\n\tDBConn *gorm.DB\n)\n"
  },
  {
    "path": "manager/backend/model/remote_url_model/remote_url_model.go",
    "content": "package remote_url_model\n\ntype PageUrl struct {\n\tId      uint32 `json:\"id\"`\n\tTitle   string `json:\"title\" validate:\"required,max=32\"`\n\tPageUrl string `json:\"page_url\" validate:\"required,max=512\"`\n}\n"
  },
  {
    "path": "manager/backend/model/session.go",
    "content": "package model\n\nimport \"github.com/gofiber/fiber/v2/middleware/session\"\n\nvar (\n\tSessionStore *session.Store\n)\n"
  },
  {
    "path": "manager/backend/model/user_model/user_model.go",
    "content": "package user_model\n\ntype User struct {\n\tId    uint32 `json:\"id\"`\n\tUser  string `json:\"user\" validate:\"required,max=16\"`\n\tToken string `json:\"token\" validate:\"required,max=32\"`\n}\n"
  },
  {
    "path": "manager/frontend/.babelrc",
    "content": "{\n  \"presets\": [\n    [\"env\", {\n      \"modules\": false,\n      \"targets\": {\n        \"browsers\": [\"> 1%\", \"last 2 versions\", \"not ie <= 8\"]\n      }\n    }],\n    \"stage-2\"\n  ],\n  \"plugins\": [\"transform-vue-jsx\", \"transform-runtime\"]\n}\n"
  },
  {
    "path": "manager/frontend/.editorconfig",
    "content": "root = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\nend_of_line = lf\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n"
  },
  {
    "path": "manager/frontend/.gitignore",
    "content": ".DS_Store\nnode_modules/\n/dist/\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# Editor directories and files\n.idea\n.vscode\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n"
  },
  {
    "path": "manager/frontend/.postcssrc.js",
    "content": "// https://github.com/michael-ciniawsky/postcss-load-config\n\nmodule.exports = {\n  \"plugins\": {\n    \"postcss-import\": {},\n    \"postcss-url\": {},\n    // to edit target browsers: use \"browserslist\" field in package.json\n    \"autoprefixer\": {}\n  }\n}\n"
  },
  {
    "path": "manager/frontend/README.md",
    "content": "# frontend\n\n> moyu manager\n\n## Build Setup\n\n``` bash\n# install dependencies\nnpm install\n\n# serve with hot reload at localhost:8080\nnpm run dev\n\n# build for production with minification\nnpm run build\n\n# build for production and view the bundle analyzer report\nnpm run build --report\n```\n\nFor a detailed explanation on how things work, check out the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader).\n"
  },
  {
    "path": "manager/frontend/build/build.js",
    "content": "'use strict'\nrequire('./check-versions')()\n\nprocess.env.NODE_ENV = 'production'\n\nconst ora = require('ora')\nconst rm = require('rimraf')\nconst path = require('path')\nconst chalk = require('chalk')\nconst webpack = require('webpack')\nconst config = require('../config')\nconst webpackConfig = require('./webpack.prod.conf')\n\nconst spinner = ora('building for production...')\nspinner.start()\n\nrm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {\n  if (err) throw err\n  webpack(webpackConfig, (err, stats) => {\n    spinner.stop()\n    if (err) throw err\n    process.stdout.write(stats.toString({\n      colors: true,\n      modules: false,\n      children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build.\n      chunks: false,\n      chunkModules: false\n    }) + '\\n\\n')\n\n    if (stats.hasErrors()) {\n      console.log(chalk.red('  Build failed with errors.\\n'))\n      process.exit(1)\n    }\n\n    console.log(chalk.cyan('  Build complete.\\n'))\n    console.log(chalk.yellow(\n      '  Tip: built files are meant to be served over an HTTP server.\\n' +\n      '  Opening index.html over file:// won\\'t work.\\n'\n    ))\n  })\n})\n"
  },
  {
    "path": "manager/frontend/build/check-versions.js",
    "content": "'use strict'\nconst chalk = require('chalk')\nconst semver = require('semver')\nconst packageConfig = require('../package.json')\nconst shell = require('shelljs')\n\nfunction exec (cmd) {\n  return require('child_process').execSync(cmd).toString().trim()\n}\n\nconst versionRequirements = [\n  {\n    name: 'node',\n    currentVersion: semver.clean(process.version),\n    versionRequirement: packageConfig.engines.node\n  }\n]\n\nif (shell.which('npm')) {\n  versionRequirements.push({\n    name: 'npm',\n    currentVersion: exec('npm --version'),\n    versionRequirement: packageConfig.engines.npm\n  })\n}\n\nmodule.exports = function () {\n  const warnings = []\n\n  for (let i = 0; i < versionRequirements.length; i++) {\n    const mod = versionRequirements[i]\n\n    if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {\n      warnings.push(mod.name + ': ' +\n        chalk.red(mod.currentVersion) + ' should be ' +\n        chalk.green(mod.versionRequirement)\n      )\n    }\n  }\n\n  if (warnings.length) {\n    console.log('')\n    console.log(chalk.yellow('To use this template, you must update following to modules:'))\n    console.log()\n\n    for (let i = 0; i < warnings.length; i++) {\n      const warning = warnings[i]\n      console.log('  ' + warning)\n    }\n\n    console.log()\n    process.exit(1)\n  }\n}\n"
  },
  {
    "path": "manager/frontend/build/utils.js",
    "content": "'use strict'\nconst path = require('path')\nconst config = require('../config')\nconst ExtractTextPlugin = require('extract-text-webpack-plugin')\nconst packageConfig = require('../package.json')\n\nexports.assetsPath = function (_path) {\n  const assetsSubDirectory = process.env.NODE_ENV === 'production'\n    ? config.build.assetsSubDirectory\n    : config.dev.assetsSubDirectory\n\n  return path.posix.join(assetsSubDirectory, _path)\n}\n\nexports.cssLoaders = function (options) {\n  options = options || {}\n\n  const cssLoader = {\n    loader: 'css-loader',\n    options: {\n      sourceMap: options.sourceMap\n    }\n  }\n\n  const postcssLoader = {\n    loader: 'postcss-loader',\n    options: {\n      sourceMap: options.sourceMap\n    }\n  }\n\n  // generate loader string to be used with extract text plugin\n  function generateLoaders (loader, loaderOptions) {\n    const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader]\n\n    if (loader) {\n      loaders.push({\n        loader: loader + '-loader',\n        options: Object.assign({}, loaderOptions, {\n          sourceMap: options.sourceMap\n        })\n      })\n    }\n\n    // Extract CSS when that option is specified\n    // (which is the case during production build)\n    if (options.extract) {\n      return ExtractTextPlugin.extract({\n        use: loaders,\n        fallback: 'vue-style-loader'\n      })\n    } else {\n      return ['vue-style-loader'].concat(loaders)\n    }\n  }\n\n  // https://vue-loader.vuejs.org/en/configurations/extract-css.html\n  return {\n    css: generateLoaders(),\n    postcss: generateLoaders(),\n    less: generateLoaders('less'),\n    sass: generateLoaders('sass', { indentedSyntax: true }),\n    scss: generateLoaders('sass'),\n    stylus: generateLoaders('stylus'),\n    styl: generateLoaders('stylus')\n  }\n}\n\n// Generate loaders for standalone style files (outside of .vue)\nexports.styleLoaders = function (options) {\n  const output = []\n  const loaders = exports.cssLoaders(options)\n\n  for (const extension in loaders) {\n    const loader = loaders[extension]\n    output.push({\n      test: new RegExp('\\\\.' + extension + '$'),\n      use: loader\n    })\n  }\n\n  return output\n}\n\nexports.createNotifierCallback = () => {\n  const notifier = require('node-notifier')\n\n  return (severity, errors) => {\n    if (severity !== 'error') return\n\n    const error = errors[0]\n    const filename = error.file && error.file.split('!').pop()\n\n    notifier.notify({\n      title: packageConfig.name,\n      message: severity + ': ' + error.name,\n      subtitle: filename || '',\n      icon: path.join(__dirname, 'logo.png')\n    })\n  }\n}\n"
  },
  {
    "path": "manager/frontend/build/vue-loader.conf.js",
    "content": "'use strict'\nconst utils = require('./utils')\nconst config = require('../config')\nconst isProduction = process.env.NODE_ENV === 'production'\nconst sourceMapEnabled = isProduction\n  ? config.build.productionSourceMap\n  : config.dev.cssSourceMap\n\nmodule.exports = {\n  loaders: utils.cssLoaders({\n    sourceMap: sourceMapEnabled,\n    extract: isProduction\n  }),\n  cssSourceMap: sourceMapEnabled,\n  cacheBusting: config.dev.cacheBusting,\n  transformToRequire: {\n    video: ['src', 'poster'],\n    source: 'src',\n    img: 'src',\n    image: 'xlink:href'\n  }\n}\n"
  },
  {
    "path": "manager/frontend/build/webpack.base.conf.js",
    "content": "'use strict'\nconst path = require('path')\nconst utils = require('./utils')\nconst config = require('../config')\nconst vueLoaderConfig = require('./vue-loader.conf')\n\nfunction resolve (dir) {\n  return path.join(__dirname, '..', dir)\n}\n\n\n\nmodule.exports = {\n  context: path.resolve(__dirname, '../'),\n  entry: {\n    app: './src/main.js'\n  },\n  output: {\n    path: config.build.assetsRoot,\n    filename: '[name].js',\n    publicPath: process.env.NODE_ENV === 'production'\n      ? config.build.assetsPublicPath\n      : config.dev.assetsPublicPath\n  },\n  resolve: {\n    extensions: ['.js', '.vue', '.json'],\n    alias: {\n      'vue$': 'vue/dist/vue.esm.js',\n      '@': resolve('src'),\n    }\n  },\n  module: {\n    rules: [\n      {\n        test: /\\.vue$/,\n        loader: 'vue-loader',\n        options: vueLoaderConfig\n      },\n      {\n        test: /\\.js$/,\n        loader: 'babel-loader',\n        include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]\n      },\n      {\n        test: /\\.(png|jpe?g|gif|svg)(\\?.*)?$/,\n        loader: 'url-loader',\n        options: {\n          limit: 10000,\n          name: utils.assetsPath('img/[name].[hash:7].[ext]')\n        }\n      },\n      {\n        test: /\\.(mp4|webm|ogg|mp3|wav|flac|aac)(\\?.*)?$/,\n        loader: 'url-loader',\n        options: {\n          limit: 10000,\n          name: utils.assetsPath('media/[name].[hash:7].[ext]')\n        }\n      },\n      {\n        test: /\\.(woff2?|eot|ttf|otf)(\\?.*)?$/,\n        loader: 'url-loader',\n        options: {\n          limit: 10000,\n          name: utils.assetsPath('fonts/[name].[hash:7].[ext]')\n        }\n      }\n    ]\n  },\n  node: {\n    // prevent webpack from injecting useless setImmediate polyfill because Vue\n    // source contains it (although only uses it if it's native).\n    setImmediate: false,\n    // prevent webpack from injecting mocks to Node native modules\n    // that does not make sense for the client\n    dgram: 'empty',\n    fs: 'empty',\n    net: 'empty',\n    tls: 'empty',\n    child_process: 'empty'\n  }\n}\n"
  },
  {
    "path": "manager/frontend/build/webpack.dev.conf.js",
    "content": "'use strict'\nconst utils = require('./utils')\nconst webpack = require('webpack')\nconst config = require('../config')\nconst merge = require('webpack-merge')\nconst path = require('path')\nconst baseWebpackConfig = require('./webpack.base.conf')\nconst CopyWebpackPlugin = require('copy-webpack-plugin')\nconst HtmlWebpackPlugin = require('html-webpack-plugin')\nconst FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')\nconst portfinder = require('portfinder')\n\nconst HOST = process.env.HOST\nconst PORT = process.env.PORT && Number(process.env.PORT)\n\nconst devWebpackConfig = merge(baseWebpackConfig, {\n  module: {\n    rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true })\n  },\n  // cheap-module-eval-source-map is faster for development\n  devtool: config.dev.devtool,\n\n  // these devServer options should be customized in /config/index.js\n  devServer: {\n    clientLogLevel: 'warning',\n    historyApiFallback: {\n      rewrites: [\n        { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') },\n      ],\n    },\n    hot: true,\n    contentBase: false, // since we use CopyWebpackPlugin.\n    compress: true,\n    host: HOST || config.dev.host,\n    port: PORT || config.dev.port,\n    open: config.dev.autoOpenBrowser,\n    overlay: config.dev.errorOverlay\n      ? { warnings: false, errors: true }\n      : false,\n    publicPath: config.dev.assetsPublicPath,\n    proxy: config.dev.proxyTable,\n    quiet: true, // necessary for FriendlyErrorsPlugin\n    watchOptions: {\n      poll: config.dev.poll,\n    }\n  },\n  plugins: [\n    new webpack.DefinePlugin({\n      'process.env': require('../config/dev.env')\n    }),\n    new webpack.HotModuleReplacementPlugin(),\n    new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update.\n    new webpack.NoEmitOnErrorsPlugin(),\n    // https://github.com/ampedandwired/html-webpack-plugin\n    new HtmlWebpackPlugin({\n      filename: 'index.html',\n      template: 'index.html',\n      inject: true\n    }),\n    // copy custom static assets\n    new CopyWebpackPlugin([\n      {\n        from: path.resolve(__dirname, '../static'),\n        to: config.dev.assetsSubDirectory,\n        ignore: ['.*']\n      }\n    ])\n  ]\n})\n\nmodule.exports = new Promise((resolve, reject) => {\n  portfinder.basePort = process.env.PORT || config.dev.port\n  portfinder.getPort((err, port) => {\n    if (err) {\n      reject(err)\n    } else {\n      // publish the new Port, necessary for e2e tests\n      process.env.PORT = port\n      // add port to devServer config\n      devWebpackConfig.devServer.port = port\n\n      // Add FriendlyErrorsPlugin\n      devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({\n        compilationSuccessInfo: {\n          messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`],\n        },\n        onErrors: config.dev.notifyOnErrors\n        ? utils.createNotifierCallback()\n        : undefined\n      }))\n\n      resolve(devWebpackConfig)\n    }\n  })\n})\n"
  },
  {
    "path": "manager/frontend/build/webpack.prod.conf.js",
    "content": "'use strict'\nconst path = require('path')\nconst utils = require('./utils')\nconst webpack = require('webpack')\nconst config = require('../config')\nconst merge = require('webpack-merge')\nconst baseWebpackConfig = require('./webpack.base.conf')\nconst CopyWebpackPlugin = require('copy-webpack-plugin')\nconst HtmlWebpackPlugin = require('html-webpack-plugin')\nconst ExtractTextPlugin = require('extract-text-webpack-plugin')\nconst OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')\nconst UglifyJsPlugin = require('uglifyjs-webpack-plugin')\n\nconst env = require('../config/prod.env')\n\nconst webpackConfig = merge(baseWebpackConfig, {\n  module: {\n    rules: utils.styleLoaders({\n      sourceMap: config.build.productionSourceMap,\n      extract: true,\n      usePostCSS: true\n    })\n  },\n  devtool: config.build.productionSourceMap ? config.build.devtool : false,\n  output: {\n    path: config.build.assetsRoot,\n    filename: utils.assetsPath('js/[name].[chunkhash].js'),\n    chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')\n  },\n  plugins: [\n    // http://vuejs.github.io/vue-loader/en/workflow/production.html\n    new webpack.DefinePlugin({\n      'process.env': env\n    }),\n    new UglifyJsPlugin({\n      uglifyOptions: {\n        compress: {\n          warnings: false\n        }\n      },\n      sourceMap: config.build.productionSourceMap,\n      parallel: true\n    }),\n    // extract css into its own file\n    new ExtractTextPlugin({\n      filename: utils.assetsPath('css/[name].[contenthash].css'),\n      // Setting the following option to `false` will not extract CSS from codesplit chunks.\n      // Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack.\n      // It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`, \n      // increasing file size: https://github.com/vuejs-templates/webpack/issues/1110\n      allChunks: true,\n    }),\n    // Compress extracted CSS. We are using this plugin so that possible\n    // duplicated CSS from different components can be deduped.\n    new OptimizeCSSPlugin({\n      cssProcessorOptions: config.build.productionSourceMap\n        ? { safe: true, map: { inline: false } }\n        : { safe: true }\n    }),\n    // generate dist index.html with correct asset hash for caching.\n    // you can customize output by editing /index.html\n    // see https://github.com/ampedandwired/html-webpack-plugin\n    new HtmlWebpackPlugin({\n      filename: config.build.index,\n      template: 'index.html',\n      inject: true,\n      minify: {\n        removeComments: true,\n        collapseWhitespace: true,\n        removeAttributeQuotes: true\n        // more options:\n        // https://github.com/kangax/html-minifier#options-quick-reference\n      },\n      // necessary to consistently work with multiple chunks via CommonsChunkPlugin\n      chunksSortMode: 'dependency'\n    }),\n    // keep module.id stable when vendor modules does not change\n    new webpack.HashedModuleIdsPlugin(),\n    // enable scope hoisting\n    new webpack.optimize.ModuleConcatenationPlugin(),\n    // split vendor js into its own file\n    new webpack.optimize.CommonsChunkPlugin({\n      name: 'vendor',\n      minChunks (module) {\n        // any required modules inside node_modules are extracted to vendor\n        return (\n          module.resource &&\n          /\\.js$/.test(module.resource) &&\n          module.resource.indexOf(\n            path.join(__dirname, '../node_modules')\n          ) === 0\n        )\n      }\n    }),\n    // extract webpack runtime and module manifest to its own file in order to\n    // prevent vendor hash from being updated whenever app bundle is updated\n    new webpack.optimize.CommonsChunkPlugin({\n      name: 'manifest',\n      minChunks: Infinity\n    }),\n    // This instance extracts shared chunks from code splitted chunks and bundles them\n    // in a separate chunk, similar to the vendor chunk\n    // see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk\n    new webpack.optimize.CommonsChunkPlugin({\n      name: 'app',\n      async: 'vendor-async',\n      children: true,\n      minChunks: 3\n    }),\n\n    // copy custom static assets\n    new CopyWebpackPlugin([\n      {\n        from: path.resolve(__dirname, '../static'),\n        to: config.build.assetsSubDirectory,\n        ignore: ['.*']\n      }\n    ])\n  ]\n})\n\nif (config.build.productionGzip) {\n  const CompressionWebpackPlugin = require('compression-webpack-plugin')\n\n  webpackConfig.plugins.push(\n    new CompressionWebpackPlugin({\n      asset: '[path].gz[query]',\n      algorithm: 'gzip',\n      test: new RegExp(\n        '\\\\.(' +\n        config.build.productionGzipExtensions.join('|') +\n        ')$'\n      ),\n      threshold: 10240,\n      minRatio: 0.8\n    })\n  )\n}\n\nif (config.build.bundleAnalyzerReport) {\n  const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin\n  webpackConfig.plugins.push(new BundleAnalyzerPlugin())\n}\n\nmodule.exports = webpackConfig\n"
  },
  {
    "path": "manager/frontend/config/dev.env.js",
    "content": "'use strict'\nconst merge = require('webpack-merge')\nconst prodEnv = require('./prod.env')\n\nmodule.exports = merge(prodEnv, {\n  NODE_ENV: '\"development\"'\n})\n"
  },
  {
    "path": "manager/frontend/config/index.js",
    "content": "'use strict'\n// Template version: 1.3.1\n// see http://vuejs-templates.github.io/webpack for documentation.\n\nconst path = require('path')\n\nmodule.exports = {\n  dev: {\n\n    // Paths\n    assetsSubDirectory: 'static',\n    assetsPublicPath: './',\n    proxyTable: {},\n\n    // Various Dev Server settings\n    host: 'localhost', // can be overwritten by process.env.HOST\n    port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined\n    autoOpenBrowser: false,\n    errorOverlay: true,\n    notifyOnErrors: true,\n    poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-\n\n    \n    /**\n     * Source Maps\n     */\n\n    // https://webpack.js.org/configuration/devtool/#development\n    devtool: 'cheap-module-eval-source-map',\n\n    // If you have problems debugging vue-files in devtools,\n    // set this to false - it *may* help\n    // https://vue-loader.vuejs.org/en/options.html#cachebusting\n    cacheBusting: true,\n\n    cssSourceMap: true\n  },\n\n  build: {\n    // Template for index.html\n    index: path.resolve(__dirname, '../dist/index.html'),\n\n    // Paths\n    assetsRoot: path.resolve(__dirname, '../dist'),\n    assetsSubDirectory: 'static',\n    assetsPublicPath: './',\n\n    /**\n     * Source Maps\n     */\n\n    productionSourceMap: true,\n    // https://webpack.js.org/configuration/devtool/#production\n    devtool: '#source-map',\n\n    // Gzip off by default as many popular static hosts such as\n    // Surge or Netlify already gzip all static assets for you.\n    // Before setting to `true`, make sure to:\n    // npm install --save-dev compression-webpack-plugin\n    productionGzip: false,\n    productionGzipExtensions: ['js', 'css'],\n\n    // Run the build command with an extra argument to\n    // View the bundle analyzer report after build finishes:\n    // `npm run build --report`\n    // Set to `true` or `false` to always turn it on or off\n    bundleAnalyzerReport: process.env.npm_config_report\n  }\n}\n"
  },
  {
    "path": "manager/frontend/config/prod.env.js",
    "content": "'use strict'\nmodule.exports = {\n  NODE_ENV: '\"production\"'\n}\n"
  },
  {
    "path": "manager/frontend/index.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n    <title>Moyu Manager</title>\n  </head>\n  <body>\n    <div id=\"app\"></div>\n    <!-- built files will be auto injected -->\n  </body>\n</html>\n"
  },
  {
    "path": "manager/frontend/package.json",
    "content": "{\n  \"name\": \"frontend\",\n  \"version\": \"1.0.0\",\n  \"description\": \"moyu manager\",\n  \"author\": \"liuxu <i@liuxu.me>\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"webpack-dev-server --inline --progress --config build/webpack.dev.conf.js\",\n    \"start\": \"npm run dev\",\n    \"build\": \"node build/build.js\",\n    \"postbuild\": \"rm -rf ../backend/dist && mv dist ../backend/\"\n  },\n  \"dependencies\": {\n    \"axios\": \"^1.3.2\",\n    \"element-ui\": \"^2.15.12\",\n    \"modern-normalize\": \"^1.1.0\",\n    \"nes.css\": \"^2.3.0\",\n    \"vue\": \"^2.5.2\",\n    \"vue-cookies\": \"^1.8.2\",\n    \"vue-router\": \"^3.0.1\"\n  },\n  \"devDependencies\": {\n    \"autoprefixer\": \"^7.1.2\",\n    \"babel-core\": \"^6.22.1\",\n    \"babel-helper-vue-jsx-merge-props\": \"^2.0.3\",\n    \"babel-loader\": \"^7.1.1\",\n    \"babel-plugin-syntax-jsx\": \"^6.18.0\",\n    \"babel-plugin-transform-runtime\": \"^6.22.0\",\n    \"babel-plugin-transform-vue-jsx\": \"^3.5.0\",\n    \"babel-preset-env\": \"^1.3.2\",\n    \"babel-preset-stage-2\": \"^6.22.0\",\n    \"chalk\": \"^2.0.1\",\n    \"copy-webpack-plugin\": \"^4.0.1\",\n    \"css-loader\": \"^0.28.0\",\n    \"extract-text-webpack-plugin\": \"^3.0.0\",\n    \"file-loader\": \"^1.1.4\",\n    \"friendly-errors-webpack-plugin\": \"^1.6.1\",\n    \"html-webpack-plugin\": \"^2.30.1\",\n    \"node-notifier\": \"^5.1.2\",\n    \"optimize-css-assets-webpack-plugin\": \"^3.2.0\",\n    \"ora\": \"^1.2.0\",\n    \"portfinder\": \"^1.0.13\",\n    \"postcss-import\": \"^11.0.0\",\n    \"postcss-loader\": \"^2.0.8\",\n    \"postcss-url\": \"^7.2.1\",\n    \"rimraf\": \"^2.6.0\",\n    \"semver\": \"^5.3.0\",\n    \"shelljs\": \"^0.7.6\",\n    \"uglifyjs-webpack-plugin\": \"^1.1.1\",\n    \"url-loader\": \"^0.5.8\",\n    \"vue-loader\": \"^13.3.0\",\n    \"vue-style-loader\": \"^3.0.1\",\n    \"vue-template-compiler\": \"^2.5.2\",\n    \"webpack\": \"^3.6.0\",\n    \"webpack-bundle-analyzer\": \"^2.9.0\",\n    \"webpack-dev-server\": \"^2.9.1\",\n    \"webpack-merge\": \"^4.1.0\"\n  },\n  \"engines\": {\n    \"node\": \">= 6.0.0\",\n    \"npm\": \">= 3.0.0\"\n  },\n  \"browserslist\": [\n    \"> 1%\",\n    \"last 2 versions\",\n    \"not ie <= 8\"\n  ]\n}\n"
  },
  {
    "path": "manager/frontend/src/App.vue",
    "content": "<template>\n  <div id=\"app\">\n    <router-view/>\n  </div>\n</template>\n\n<script>\nexport default {\n  name: 'App'\n}\n</script>\n\n<style>\n@import url('https://fonts.googleapis.com/css?family=Press+Start+2P');\n#app {\n  background: #FFFFFA;\n}\n</style>\n"
  },
  {
    "path": "manager/frontend/src/components/Login.vue",
    "content": "<template>\n    <div class=\"nes-container with-title is-centered\">\n        <p class=\"title\">Login</p>\n        <div class=\"nes-field is-inline\">\n            <label for=\"inline_field\">Username:</label>\n            <input type=\"text\" id=\"inline_field\" class=\"nes-input is-success\">\n        </div>\n        <div class=\"nes-field is-inline\">\n            <label for=\"inline_field\">Password:</label>\n            <input type=\"text\" id=\"inline_field\" class=\"nes-input is-success\">\n        </div>\n    </div>\n</template>\n  \n<script>\n    export default {\n        name: 'HelloWorld',\n        data () {\n            return {\n            msg: 'Welcome to Your Vue.js App'\n            }\n        }\n    }\n</script>\n\n<style scoped>\n</style>"
  },
  {
    "path": "manager/frontend/src/components/MoyuFooter.vue",
    "content": "<template>\n    <div class=\"moyu-footer\">\n        <p class=\"powered-by\">Powered By: liuxu</p>\n    </div>\n</template>\n\n<style scoped>\n.moyu-footer {\n    display: flex;\n}\n.powered-by {\n    margin-left: auto;\n    margin-right: 10px;\n}\n</style>"
  },
  {
    "path": "manager/frontend/src/components/MoyuNav.vue",
    "content": "<template>\n    <div class=\"moyu-nav\">\n        <div class=\"nav-left\">\n            <div class=\"logo\">\n                <a href=\"/#/\"><span>MoYu</span></a>\n            </div>\n        </div>\n        <div class=\"nav-right\">\n            <div class=\"btn\" v-if=\"logged\">\n                <button type=\"button\" class=\"nes-btn\" @click=\"logout\">Logout</button>\n            </div>\n            <div class=\"btn\" v-else>\n                <button type=\"button\" class=\"nes-btn is-primary\" @click=\"login\">Login</button>\n            </div>\n            <div class=\"github-logo\">\n                <a href=\"https://github.com/liuquanhao/moyu\" target=\"_blank\" rel=\"noopener\">\n                    <i class=\"nes-icon github is-medium\"></i>\n                </a>\n            </div>\n        </div>\n    </div>\n</template>\n\n<script>\n    export default {\n        name: \"moyu-nav\",\n        data: function() {\n            return {\n                isClick: true\n            }\n        },\n        methods: {\n            login: function() {\n                this.$router.push('/login')\n            },\n            logout: function() {\n                this.isClick = true\n                this.axios.post('./api/logout', {}, {withCredentials: true}).then(res => {\n                    this.$cookies.remove('uid')\n                    this.isClick = false\n                    if (this.$route.name == 'MainPage') {\n                        this.$router.go(0)\n                    } else {\n                        this.$router.push('/')\n                    }\n                }).catch(err => {\n                    this.$cookies.remove('uid')\n                    this.isClick = false\n                    if (this.$route.name == 'MainPage') {\n                        this.$router.go(0)\n                    } else {\n                        this.$router.push('/')\n                    }\n                });\n            }\n        },\n        computed: {\n            logged: function() {\n                return this.$cookies.get('uid')\n            }\n        }\n    }\n</script>\n\n<style scoped>\n.logo a {\n    color: black;\n    text-decoration: none;\n}\n.moyu-nav {\n    display: flex;\n    margin-top: 10px;\n    margin-bottom: 10px;\n    border-bottom: 4px solid #D3D3D3;\n}\n.nav-left {\n    display: flex;\n    width: 50%;\n}\n.nav-right {\n    display: flex;\n    justify-content: flex-end;\n    width: 50%;\n}\n.logo {\n    margin-left: 10px;\n    font-size: 30px;\n}\n.btn {\n    margin-right: 15px;\n    margin-bottom: 5px;\n}\n.github-logo {\n    margin-right: 10px;\n}\n</style>"
  },
  {
    "path": "manager/frontend/src/layouts/PageLayout.vue",
    "content": "<template>\n    <el-container class=\"container\">\n        <el-header>\n            <moyu-nav></moyu-nav>\n        </el-header>\n        <el-main>\n            <slot></slot>\n        </el-main>\n        <el-footer>\n            <moyu-footer></moyu-footer>\n        </el-footer>\n    </el-container>\n</template>\n  \n<script>\n    import MoyuNav from '../components/MoyuNav.vue'\n    import MoyuFooter from '../components/MoyuFooter.vue'\n    export default {\n        components: {\n            MoyuNav,\n            MoyuFooter,\n        },\n        data: function() {\n            return {}\n        }\n    }\n</script>\n  \n<style scoped>\n.container {\n    min-width: 780px;\n    max-width: 1260px;\n    margin: 0px auto;\n    justify-content: center;\n}\n.el-main {\n    min-height: 800px;\n}\n</style>"
  },
  {
    "path": "manager/frontend/src/main.js",
    "content": "// The Vue build version to load with the `import` command\n// (runtime-only or standalone) has been set in webpack.base.conf with an alias.\nimport Vue from 'vue'\nimport App from './App'\nimport router from './router'\nimport VueCookies from 'vue-cookies'\n\nimport \"modern-normalize/modern-normalize.css\"\nimport {\n  Row,\n  Col,\n  Container,\n  Header,\n  Main,\n  Footer,\n  Table,\n  TableColumn,\n} from 'element-ui'\nimport 'element-ui/lib/theme-chalk/index.css'\nimport axios from 'axios'\nimport \"nes.css/css/nes.min.css\"\n\nVue.config.productionTip = false\nVue.prototype.axios = axios\n\nVue.component(Row.name, Row)\nVue.component(Col.name, Col)\nVue.component(Container.name, Container)\nVue.component(Header.name, Header)\nVue.component(Main.name, Main)\nVue.component(Footer.name, Footer)\nVue.component(Table.name, Table)\nVue.component(TableColumn.name, TableColumn)\n\nVue.use(VueCookies)\n\n/* eslint-disable no-new */\nnew Vue({\n  el: '#app',\n  router,\n  components: { App },\n  template: '<App/>'\n})\n"
  },
  {
    "path": "manager/frontend/src/pages/AddUrlPage.vue",
    "content": "<template>\n    <page-layout>\n        <div class=\"nes-container with-title\">\n            <p class=\"title\">Add Page Url</p>\n            <el-row><el-col><div class=\"nes-field is-inline\"><label for=\"title-ipt\">Title:</label><input type=\"text\" class=\"nes-input\" :class=\"{'is-error': titleFail}\" v-model=\"title\" placeholder=\"MoyuPage\" /></div></el-col></el-row>\n            <el-row><el-col><div class=\"nes-field is-inline\"><label>Url:</label><input type=\"text\" id=\"page-url-ipt\" class=\"nes-input\" :class=\"{'is-error': pageUrlFail, 'is-success': pageUrlSuccess}\" v-model=\"pageUrl\" placeholder=\"https://moyu.linux.plus/\" /></div></el-col></el-row>\n            <el-row>\n                <el-col :span=\"6\" :offset=\"6\">\n                    <button type=\"button\" class=\"nes-btn test-btn\" :class=\"[isClickTest ? 'is-disabled': 'is-warning']\" @click=\"urlTest\">Test</button>\n                </el-col>\n                <el-col :span=\"6\" :offset=\"4\">\n                    <button type=\"button\" class=\"nes-btn add-btn\" :class=\"[isClickAdd ? 'is-disabled': 'is-primary']\" @click=\"urlAdd\">Add</button>\n                </el-col>\n            </el-row>\n        </div>\n    </page-layout>\n</template>\n\n<script>\n    import PageLayout from '../layouts/PageLayout.vue'\n    export default {\n        components: {\n            PageLayout,\n        },\n        data: function() {\n            return {\n                title: \"\",\n                pageUrl: \"\",\n                titleFail: false,\n                pageUrlFail: false,\n                pageUrlSuccess: false,\n                isClickTest: false,\n                isClickAdd: false,\n            }\n        },\n        methods: {\n            urlTest: function() {\n                this.titleFail = false\n                this.pageUrlFail = false\n                if (!this.title) {\n                    this.titleFail = true\n                    return\n                }\n                if (!this.pageUrl) {\n                    this.pageUrlFail = true\n                    return\n                }\n                this.isClickTest = true\n                this.axios.get(this.pageUrl, {withCredentials: false}).then(res => {\n                    this.isClickTest = false\n                    this.pageUrlSuccess = true\n                }).catch(err => {\n                    this.isClickTest = false\n                    this.pageUrlFail = true\n                });\n            },\n            urlAdd: function() {\n                this.titleFail = false\n                this.pageUrlFail = false\n                if (!this.title) {\n                    this.titleFail = true\n                    return\n                }\n                if (!this.pageUrl) {\n                    this.pageUrlFail = true\n                    return\n                }\n                this.isClickAdd = true\n                this.axios.post('./api/page_url', {\n                    'title': this.title,\n                    'page_url': this.pageUrl,\n                }, {withCredentials: true}).then(res => {\n                    this.$router.push('/')\n                }).catch(err => {\n                    this.isClickAdd = false\n                    this.titleFail = true\n                    this.pageUrlFail = true\n                });\n            }\n        }\n    }\n</script>\n\n<style scoped>\n.nes-container {\n    margin: 200px auto;\n    width: 80%;\n}\n.nes-input {\n    border-image-repeat: stretch;\n}\n.el-row {\n    margin-bottom: 10px;\n}\n</style>"
  },
  {
    "path": "manager/frontend/src/pages/LoginPage.vue",
    "content": "<template>\n    <page-layout>\n        <div class=\"nes-container with-title is-centered\">\n            <p class=\"title\">Login</p>\n            <el-row><el-col><div class=\"nes-field is-inline\"><label for=\"user-ipt\">User:</label><input type=\"text\" id=\"user-ipt\" class=\"nes-input\" :class=\"{'is-error': userFail}\" v-model=\"user\" /></div></el-col></el-row>\n            <el-row><el-col><div class=\"nes-field is-inline\"><label for=\"token-ipt\">Token:</label><input type=\"password\" id=\"token-ipt\" class=\"nes-input\" :class=\"{'is-error': tokenFail}\" v-model=\"token\" /></div></el-col></el-row>\n            <el-row>\n                <el-col :span=\"6\" :offset=\"8\">\n                    <button type=\"button\" class=\"nes-btn login-btn\" :class=\"[isClick ? 'is-disabled': 'is-primary']\" @click=\"login\">Login</button>\n                </el-col>\n                <el-col :span=\"8\" class=\"forget-token-col\">\n                    <a href=\"https://github.com/liuquanhao/moyu\" target=\"_blank\" rel=\"noopener\">\n                        <span class=\"nes-text is-disabled forget-token-txt\">Forget Token?</span>\n                    </a>\n                </el-col>\n            </el-row>\n        </div>\n    </page-layout>\n</template>\n\n<script>\n    import PageLayout from '../layouts/PageLayout.vue'\n    export default {\n        components: {\n            PageLayout,\n        },\n        data: function() {\n            return {\n                user: \"\",\n                token: \"\",\n                userFail: false,\n                tokenFail: false,\n                isClick: false,\n            }\n        },\n        methods: {\n            login: function() {\n                this.userFail = false\n                this.tokenFail = false\n                if (!this.user) {\n                    this.userFail = true\n                    return\n                }\n                if (!this.token) {\n                    this.tokenFail = true\n                    return\n                }\n                this.isClick = true\n                this.axios.post('./api/login', {\n                    user: this.user,\n                    token: this.token\n                }, {withCredentials: true}).then(res => {\n                    this.isClick = false\n                    this.$router.push('/')\n                }).catch(err => {\n                    this.isClick = false\n                    this.userFail = true\n                    this.tokenFail = true\n                });\n            }\n        }\n    }\n</script>\n\n<style scoped>\n.nes-container {\n    margin: 200px auto;\n    width: 60%;\n}\n.el-row {\n    margin-bottom: 10px;\n}\n.login-btn {\n    margin-top: 10px;\n}\n.forget-token-col {\n    margin-top: 30px;\n}\n.forget-token-txt {\n    font-size: 5px;\n    text-decoration: underline;\n}\n.nes-input {\n    border-image-repeat: stretch;\n}\n</style>"
  },
  {
    "path": "manager/frontend/src/pages/MainPage.vue",
    "content": "<template>\n    <page-layout>\n        <div class=\"pages\">\n            <el-row type=\"flex\">\n                <el-col :span=\"4\"><span>Title</span></el-col>\n                <el-col :span=\"3\"><span>Uptime</span></el-col>\n                <el-col :span=\"3\"><span>CPU</span></el-col>\n                <el-col :span=\"3\"><span>Memory</span></el-col>\n                <el-col :span=\"3\"><span>Disk(/)</span></el-col>\n                <el-col :span=\"5\"><span>BW(u|d)</span></el-col>\n                <el-col :span=\"2\"><button type=\"button\" class=\"nes-btn\" :class=\"[logged ? 'is-primary' : 'is-disabled']\" @click=\"jumpTo('/add_url')\">Add</button></el-col>\n            </el-row>\n            <el-row type=\"flex\" v-for=\"page in pages\" :key=\"page.id\">\n                <el-col :span=\"4\" v-if=\"logged\"><a :href=\"page.page_url\" target=\"_blank\" rel=\"noopener\">{{ page.title }}</a></el-col>\n                <el-col :span=\"4\" v-else><span>{{ page.title }}</span></el-col>\n                <el-col :span=\"3\"><span v-if=\"page.page_data\">{{ page.page_data.uptime | humanDur }}</span></el-col>\n                <el-col :span=\"3\"><span v-if=\"page.page_data\" class=\"nes-text is-success\">{{ page.page_data.cpu_percent | humanPerc }}%</span></el-col>\n                <el-col :span=\"3\"><span v-if=\"page.page_data\" class=\"nes-text is-error\">{{ page.page_data.memory_percent | humanPerc }}%</span></el-col>\n                <el-col :span=\"3\"><span v-if=\"page.page_data\" class=\"nes-text is-primary\">{{ page.page_data.disk_percent | humanPerc }}%</span></el-col>\n                <el-col :span=\"5\">\n                    <div v-if=\"page.page_data\" class=\"nes-badge is-splited net-badge\">\n                        <span class=\"is-dark\">{{ page.page_data.net_send_rate | humanByte }}</span>\n                        <span class=\"is-warning\">{{ page.page_data.net_recv_rate | humanByte }}</span>\n                    </div>\n                </el-col>\n                <el-col :span=\"2\"><button type=\"button\" class=\"nes-btn\" :class=\"[logged ? 'is-error' : 'is-disabled']\" @click=\"deletePage(page.id, $event)\">Delete</button></el-col>\n            </el-row>\n        </div>\n    </page-layout>\n</template>\n\n<script>\n    import PageLayout from '../layouts/PageLayout.vue'\n    export default {\n        components: {\n            PageLayout,\n        },\n        data: function() {\n            return {\n                pages: [],\n            }\n        },\n        created: function() {\n            this.initData()\n            this.initWs()\n        },\n        beforeDestroy: function() {\n            this.ws.close()\n        },\n        computed: {\n            logged: function() {\n                return this.$cookies.get('uid')\n            },\n        },\n        methods: {\n            jumpTo: function(url) {\n                this.$router.push(url)\n            },\n            deletePage: function(id, event) {\n                this.btnDisabledStatus(event.target, true, 'is-error', 'is-disabled')\n                this.axios.delete('./api/page_url/' + id, {withCredentials: true})\n                .then(res => {\n                    this.btnDisabledStatus(event.target, false, 'is-disabled', 'is-error')\n                    this.dropDataPage(id)\n                    this.initWs()\n                })\n                .catch(err => {\n                    this.btnDisabledStatus(event.target, false, 'is-disabled', 'is-warning')\n                    console.log(err)\n                })\n            },\n            dropDataPage(id) {\n                this.pages = this.pages.filter(function(page) {\n                    if (page.id == id) {\n                        return false\n                    }\n                    return true\n                })\n            },\n            btnDisabledStatus(element, disabled, removeClass, addClass) {\n                element.disabled = disabled\n                element.classList.remove(removeClass)\n                element.classList.add(addClass)\n            },\n            initData() {\n                this.axios.get('./api/page_url/', {withCredentials: true})\n                .then(res => {\n                    this.pages = res.data\n                })\n                .catch(err => {\n                    console.log(err)\n                })\n            },\n            initWs() {\n                let wsProtocol = window.location.protocol == \"https:\" ? \"wss://\" : \"ws://\"\n                let wsPort = window.location.port == \"\" ? \"\" : \":\" + window.location.port\n                this.ws = new WebSocket(wsProtocol + window.location.hostname + wsPort + window.location.pathname + \"ws/page_data\")\n                this.ws.onopen = this.wsOnOpen\n                this.ws.onerror = this.wsOnError\n                this.ws.onmessage = this.wsOnMessage\n                this.ws.onclose = this.wsOnClose\n            },\n            wsOnOpen() {\n                console.log(\"ws connect success\")\n            },\n            wsOnError() {\n                console.log(\"ws connect fail\")\n                this.initWs()\n            },\n            wsOnMessage(e) {\n                let pageInfo = JSON.parse(e.data)\n                let idx = this.pages.findIndex(page => {\n                    return page.id == pageInfo.page_id\n                })\n                if (idx < 0) {\n                    return\n                }\n                let page = {}\n                page.id = this.pages[idx].id\n                page.title = this.pages[idx].title\n                page.page_url = this.pages[idx].page_url\n                pageInfo.page_data.net_send_rate = this.curNetSend(pageInfo.page_data.net_send, pageInfo.page_id, pageInfo.page_data.timestamp)\n                pageInfo.page_data.net_recv_rate = this.curNetRecv(pageInfo.page_data.net_recv, pageInfo.page_id, pageInfo.page_data.timestamp)\n                page.page_data = pageInfo.page_data\n                this.$set(this.pages, idx, page)\n            },\n            wsOnClose(e) {\n                console.log(\"ws close\")\n            },\n            curNetSend: function(curSend, id, ts) {\n                let lastSend = sessionStorage.getItem(id + \"-lastSend\")\n                let lastSendTs = sessionStorage.getItem(id + \"-lastSendTs\")\n                if (!lastSend) {\n                    lastSend = curSend\n                }\n                sessionStorage.setItem(id + \"-lastSend\", curSend)\n                sessionStorage.setItem(id + \"-lastSendTs\", ts)\n                let dur = ts - lastSendTs\n                if (dur < 1) {\n                    return 0\n                }\n                return (curSend - lastSend)/(ts - lastSendTs)\n            },\n            curNetRecv: function(curRecv, id, ts) {\n                let lastRecv = sessionStorage.getItem(id + \"-lastRecv\")\n                let lastRecvTs = sessionStorage.getItem(id + \"-lastRecvTs\")\n                if (!lastRecv) {\n                    lastRecv = curRecv\n                }\n                sessionStorage.setItem(id + \"-lastRecv\", curRecv)\n                sessionStorage.setItem(id + \"-lastRecvTs\", ts)\n                let dur = ts - lastRecvTs\n                if (dur < 1) {\n                    return 0\n                }\n                return (curRecv - lastRecv)/(ts - lastRecvTs)\n            },\n        },\n        filters: {\n            humanByte: function (size, num) {\n                if (size < 1024 * 1024) {\n                    return Math.trunc(size / 1024).toFixed(num) + \"K\"\n                } else if (size < 1024 * 1024 * 1024) {\n                    return (size / 1024 / 1024).toFixed(num) + \"M\"\n                } else {\n                    return (size / 1024 / 1024 / 1024).toFixed(num) + \"G\"\n                }\n            },\n            humanDur: function (sec) {\n                if (sec < 60) {\n                    return Math.trunc(sec) + \" s\";\n                } else if (sec < 3600) {\n                    let min = Math.trunc(sec / 60);\n                    return min + \" m\"\n                } else if (sec < 86400) {\n                    let hour = Math.trunc(sec / 3600);\n                    return hour + \" h\"\n                } else {\n                    let day = Math.trunc(sec / 86400);\n                    return day + \"d\"\n                }\n            },\n            humanPerc: function (float) {\n                if (!float) {\n                    return 0\n                }\n                return float.toFixed(1)\n            },\n        }\n    }\n</script>\n\n<style scoped>\n.pages {\n    margin: 20px 10px 0px 10px;\n}\n.el-row {\n    border-bottom: 1px;\n    border-bottom-style: solid;\n    align-items: center;\n}\n.el-col {\n    margin-top: 10px;\n    margin-bottom: 10px;\n    text-align: center;\n}\n.el-col button {\n    display: block;\n    margin: auto;\n}\n</style>"
  },
  {
    "path": "manager/frontend/src/router/index.js",
    "content": "import Vue from 'vue'\nimport Router from 'vue-router'\nimport LoginPage from '@/pages/LoginPage'\nimport MainPage from '@/pages/MainPage'\nimport AddUrlPage from '@/pages/AddUrlPage'\n\nVue.use(Router)\n\nexport default new Router({\n  routes: [\n    {\n      path: '/',\n      name: 'MainPage',\n      component: MainPage\n    },\n    {\n      path: '/login',\n      name: 'LoginPage',\n      component: LoginPage\n    },\n    {\n      path: '/add_url',\n      name: 'AddUrlPage',\n      component: AddUrlPage\n    }\n  ]\n})\n"
  },
  {
    "path": "manager/frontend/static/.gitkeep",
    "content": ""
  },
  {
    "path": "page/backend/.gitignore",
    "content": "dist/\nmoyu-page"
  },
  {
    "path": "page/backend/controller/system.go",
    "content": "package controller\n\nimport (\n\t\"time\"\n\n\t\"github.com/gofiber/fiber/v2\"\n\t\"github.com/gofiber/websocket/v2\"\n\t\"github.com/liuquanhao/moyu/service\"\n)\n\nfunc GetSysInfo(c *fiber.Ctx) error {\n\treturn c.JSON(service.GetSystemInfo())\n}\n\nfunc PushSysStatus(c *websocket.Conn) {\n\tfor {\n\t\tc.WriteJSON(service.GetSystemStatus())\n\t\ttime.Sleep(1 * time.Second)\n\t}\n}\n\nfunc GetPageData(c *fiber.Ctx) error {\n\treturn c.JSON(service.GetPageData())\n}\n"
  },
  {
    "path": "page/backend/go.mod",
    "content": "module github.com/liuquanhao/moyu\n\ngo 1.19\n\nrequire (\n\tgithub.com/bytedance/sonic v1.5.0\n\tgithub.com/gofiber/fiber/v2 v2.40.1\n\tgithub.com/gofiber/websocket/v2 v2.1.2\n\tgithub.com/shirou/gopsutil/v3 v3.22.10\n)\n\nrequire (\n\tgithub.com/andybalholm/brotli v1.0.4 // indirect\n\tgithub.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06 // indirect\n\tgithub.com/fasthttp/websocket v1.5.0 // indirect\n\tgithub.com/go-ole/go-ole v1.2.6 // indirect\n\tgithub.com/klauspost/compress v1.15.9 // indirect\n\tgithub.com/klauspost/cpuid/v2 v2.0.9 // indirect\n\tgithub.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect\n\tgithub.com/mattn/go-colorable v0.1.13 // indirect\n\tgithub.com/mattn/go-isatty v0.0.16 // indirect\n\tgithub.com/mattn/go-runewidth v0.0.14 // indirect\n\tgithub.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect\n\tgithub.com/rivo/uniseg v0.2.0 // indirect\n\tgithub.com/savsgio/gotils v0.0.0-20211223103454-d0aaa54c5899 // indirect\n\tgithub.com/tklauser/go-sysconf v0.3.11 // indirect\n\tgithub.com/tklauser/numcpus v0.6.0 // indirect\n\tgithub.com/twitchyliquid64/golang-asm v0.15.1 // indirect\n\tgithub.com/valyala/bytebufferpool v1.0.0 // indirect\n\tgithub.com/valyala/fasthttp v1.41.0 // indirect\n\tgithub.com/valyala/tcplisten v1.0.0 // indirect\n\tgithub.com/yusufpapurcu/wmi v1.2.2 // indirect\n\tgolang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect\n\tgolang.org/x/sys v0.2.0 // indirect\n)\n"
  },
  {
    "path": "page/backend/go.sum",
    "content": "github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=\ngithub.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=\ngithub.com/bytedance/sonic v1.5.0 h1:XWdTi8bwPgxIML+eNV1IwNuTROK6EUrQ65ey8yd6fRQ=\ngithub.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=\ngithub.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06 h1:1sDoSuDPWzhkdzNVxCxtIaKiAe96ESVPv8coGwc1gZ4=\ngithub.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/fasthttp/websocket v1.5.0 h1:B4zbe3xXyvIdnqjOZrafVFklCUq5ZLo/TqCt5JA1wLE=\ngithub.com/fasthttp/websocket v1.5.0/go.mod h1:n0BlOQvJdPbTuBkZT0O5+jk/sp/1/VCzquR1BehI2F4=\ngithub.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=\ngithub.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=\ngithub.com/gofiber/fiber/v2 v2.40.1 h1:pc7n9VVpGIqNsvg9IPLQhyFEMJL8gCs1kneH5D1pIl4=\ngithub.com/gofiber/fiber/v2 v2.40.1/go.mod h1:Gko04sLksnHbzLSRBFWPFdzM9Ws9pRxvvIaohJK1dsk=\ngithub.com/gofiber/websocket/v2 v2.1.2 h1:EulKyLB/fJgui5+6c8irwEnYQ9FRsrLZfkrq9OfTDGc=\ngithub.com/gofiber/websocket/v2 v2.1.2/go.mod h1:S+sKWo0xeC7Wnz5h4/8f6D/NxsrLFIdWDYB3SyVO9pE=\ngithub.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=\ngithub.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/klauspost/compress v1.14.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=\ngithub.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY=\ngithub.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=\ngithub.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=\ngithub.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=\ngithub.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=\ngithub.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=\ngithub.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=\ngithub.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=\ngithub.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=\ngithub.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=\ngithub.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=\ngithub.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=\ngithub.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=\ngithub.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=\ngithub.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=\ngithub.com/savsgio/gotils v0.0.0-20211223103454-d0aaa54c5899 h1:Orn7s+r1raRTBKLSc9DmbktTT04sL+vkzsbRD2Q8rOI=\ngithub.com/savsgio/gotils v0.0.0-20211223103454-d0aaa54c5899/go.mod h1:oejLrk1Y/5zOF+c/aHtXqn3TFlzzbAgPWg8zBiAHDas=\ngithub.com/shirou/gopsutil/v3 v3.22.10 h1:4KMHdfBRYXGF9skjDWiL4RA2N+E8dRdodU/bOZpPoVg=\ngithub.com/shirou/gopsutil/v3 v3.22.10/go.mod h1:QNza6r4YQoydyCfo6rH0blGfKahgibh4dQmV5xdFkQk=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=\ngithub.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=\ngithub.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=\ngithub.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=\ngithub.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=\ngithub.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk=\ngithub.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=\ngithub.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI=\ngithub.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ=\ngithub.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms=\ngithub.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4=\ngithub.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=\ngithub.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=\ngithub.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=\ngithub.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=\ngithub.com/valyala/fasthttp v1.33.0/go.mod h1:KJRK/MXx0J+yd0c5hlR+s1tIHD72sniU8ZJjl97LIw4=\ngithub.com/valyala/fasthttp v1.41.0 h1:zeR0Z1my1wDHTRiamBCXVglQdbUwgb9uWG3k1HQz6jY=\ngithub.com/valyala/fasthttp v1.41.0/go.mod h1:f6VbjjoI3z1NDOZOv17o6RvtRSWxC77seBFc2uWtgiY=\ngithub.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=\ngithub.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=\ngithub.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=\ngithub.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=\ngolang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU=\ngolang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=\ngolang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=\ngolang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=\ngolang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=\ngolang.org/x/net v0.0.0-20220111093109-d55c255bac03/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=\ngolang.org/x/net v0.0.0-20220906165146-f3363e06e74c/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=\ngolang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=\ngolang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=\ngolang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nrsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=\n"
  },
  {
    "path": "page/backend/main.go",
    "content": "package main\n\nimport (\n\t\"embed\"\n\t\"io/fs\"\n\t\"log\"\n\t\"net/http\"\n\t\"os\"\n\n\t\"github.com/bytedance/sonic\"\n\t\"github.com/gofiber/fiber/v2\"\n\t\"github.com/gofiber/fiber/v2/middleware/cors\"\n\t\"github.com/gofiber/fiber/v2/middleware/filesystem\"\n\t\"github.com/gofiber/websocket/v2\"\n\t\"github.com/liuquanhao/moyu/controller\"\n\t\"github.com/liuquanhao/moyu/middleware\"\n)\n\n//go:embed dist\nvar frontend embed.FS\n\nfunc main() {\n\tapp := fiber.New(fiber.Config{\n\t\tJSONEncoder: sonic.Marshal,\n\t\tJSONDecoder: sonic.Unmarshal,\n\t})\n\tapp.Use(cors.New())\n\tbase := app.Group(os.Getenv(\"BASEURL\"))\n\tws := base.Group(\"/ws\")\n\tws.Use(\"/*\", middleware.UpgradeOptions)\n\tws.Get(\"/sys_status\", websocket.New(controller.PushSysStatus))\n\n\tapi := base.Group(\"/api\")\n\tapi.Get(\"/sys_info\", controller.GetSysInfo)\n\tapi.Get(\"/page_data\", controller.GetPageData)\n\n\tstripped, err := fs.Sub(frontend, \"dist\")\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tbase.Use(\"/\", filesystem.New(filesystem.Config{\n\t\tRoot:   http.FS(stripped),\n\t\tBrowse: true,\n\t}))\n\n\thost := os.Getenv(\"HOST\")\n\tport := os.Getenv(\"PORT\")\n\tlog.Println(host + \":\" + port)\n\tlog.Fatal(app.Listen(host + \":\" + port))\n}\n"
  },
  {
    "path": "page/backend/middleware/ws_options.go",
    "content": "package middleware\n\nimport (\n\t\"github.com/gofiber/fiber/v2\"\n\t\"github.com/gofiber/websocket/v2\"\n)\n\nfunc UpgradeOptions(c *fiber.Ctx) error {\n\tif websocket.IsWebSocketUpgrade(c) {\n\t\treturn c.Next()\n\t}\n\treturn fiber.ErrUpgradeRequired\n}\n"
  },
  {
    "path": "page/backend/service/cpu.go",
    "content": "package service\n\nimport \"github.com/shirou/gopsutil/v3/cpu\"\n\ntype Core struct {\n\tCPU       int32    `json:\"cpu\"`\n\tCoreID    string   `json:\"core_id\"`\n\tModelName string   `json:\"model\"`\n\tMhz       float64  `json:\"mhz\"`\n\tFlags     []string `json:\"flags\"`\n}\ntype CPUInfo struct {\n\tCount int     `json:\"count\"`\n\tCores []*Core `json:\"cores\"`\n}\n\nfunc (cpuInfo *CPUInfo) AppendCore(core *Core) {\n\tcpuInfo.Cores = append(cpuInfo.Cores, core)\n}\n\nfunc GetCPUInfo() *CPUInfo {\n\tcpuInfoStats, _ := cpu.Info()\n\tcpuInfo := new(CPUInfo)\n\tcpuInfo.Count, _ = cpu.Counts(true)\n\tfor _, infoStat := range cpuInfoStats {\n\t\tcore := &Core{\n\t\t\tCPU:       infoStat.CPU,\n\t\t\tCoreID:    infoStat.CoreID,\n\t\t\tModelName: infoStat.ModelName,\n\t\t\tMhz:       infoStat.Mhz,\n\t\t\tFlags:     infoStat.Flags,\n\t\t}\n\t\tcpuInfo.AppendCore(core)\n\t}\n\treturn cpuInfo\n}\n"
  },
  {
    "path": "page/backend/service/disk.go",
    "content": "package service\n\nimport (\n\t\"strings\"\n\n\t\"github.com/shirou/gopsutil/v3/disk\"\n)\n\ntype Partition struct {\n\tDevice      string  `json:\"device\"`\n\tMountPoint  string  `json:\"mount_point\"`\n\tSize        uint64  `json:\"size\"`\n\tUsedPercent float64 `json:\"used_percent\"`\n}\ntype DiskInfo struct {\n\tPartitions []*Partition `json:\"partitions\"`\n}\n\nfunc (diskInfo *DiskInfo) AppendPartition(partition *Partition) {\n\tdiskInfo.Partitions = append(diskInfo.Partitions, partition)\n}\n\nfunc GetDiskInfo() *DiskInfo {\n\tdiskInfo := new(DiskInfo)\n\tpartitionStats, _ := disk.Partitions(false)\n\tfor _, partitionStat := range partitionStats {\n\t\tif strings.HasPrefix(partitionStat.Mountpoint, \"/snap\") || strings.HasPrefix(partitionStat.Mountpoint, \"/loop\") {\n\t\t\tcontinue\n\t\t}\n\t\tusageStat, err := disk.Usage(partitionStat.Mountpoint)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tpartition := &Partition{\n\t\t\tDevice:      partitionStat.Device,\n\t\t\tMountPoint:  partitionStat.Mountpoint,\n\t\t\tSize:        usageStat.Total,\n\t\t\tUsedPercent: usageStat.UsedPercent,\n\t\t}\n\t\tdiskInfo.AppendPartition(partition)\n\t}\n\treturn diskInfo\n}\n"
  },
  {
    "path": "page/backend/service/host.go",
    "content": "package service\n\nimport (\n\t\"github.com/shirou/gopsutil/v3/host\"\n)\n\ntype HostInfo struct {\n\tHostname        string `json:\"hostname\"`\n\tDistribution    string `json:\"distribution\"`\n\tArch            string `json:\"arch\"`\n\tKernel          string `json:\"kernel\"`\n\tVirtualPlatform string `json:\"virtual_platform\"`\n\tUptime          uint64 `json:\"uptime\"`\n}\n\nfunc GetHostInfo() *HostInfo {\n\thostInfo, _ := host.Info()\n\treturn &HostInfo{\n\t\tHostname:        hostInfo.Hostname,\n\t\tDistribution:    hostInfo.Platform + \" \" + hostInfo.PlatformVersion,\n\t\tArch:            hostInfo.KernelArch,\n\t\tKernel:          hostInfo.KernelVersion,\n\t\tVirtualPlatform: hostInfo.VirtualizationSystem,\n\t\tUptime:          hostInfo.Uptime,\n\t}\n}\n\nfunc GetUptime() uint64 {\n\thostInfo, _ := host.Info()\n\treturn hostInfo.Uptime\n}\n"
  },
  {
    "path": "page/backend/service/memory.go",
    "content": "package service\n\nimport (\n\t\"github.com/shirou/gopsutil/v3/mem\"\n)\n\ntype MemoryInfo struct {\n\tMemory uint64 `json:\"memory\"`\n\tSwap   uint64 `json:\"swap\"`\n}\n\ntype MemoryStatus struct {\n\tMemoryPercent float64 `json:\"memory_percent\"`\n\tSwapPercent   float64 `json:\"swap_percent\"`\n}\n\nfunc GetMemoryInfo() *MemoryInfo {\n\tvmStat, _ := mem.VirtualMemory()\n\tswapStat, _ := mem.SwapMemory()\n\treturn &MemoryInfo{\n\t\tMemory: vmStat.Total,\n\t\tSwap:   swapStat.Total,\n\t}\n}\n\nfunc GetMemoryStatus() *MemoryStatus {\n\tmemInfo, _ := mem.VirtualMemory()\n\tswapInfo, _ := mem.SwapMemory()\n\treturn &MemoryStatus{\n\t\tMemoryPercent: memInfo.UsedPercent,\n\t\tSwapPercent:   swapInfo.UsedPercent,\n\t}\n}\n"
  },
  {
    "path": "page/backend/service/network.go",
    "content": "package service\n\nimport (\n\t\"regexp\"\n\n\t\"github.com/shirou/gopsutil/v3/net\"\n)\n\ntype Ifce struct {\n\tName     string `json:\"name\"`\n\tByteSend uint64 `json:\"send_byte\"`\n\tByteRecv uint64 `json:\"recv_byte\"`\n}\n\ntype NetworkInfo struct {\n\tIfces []*Ifce `json:\"ifces\"`\n}\n\nfunc (networkInfo *NetworkInfo) AppendIfce(ifce *Ifce) {\n\tnetworkInfo.Ifces = append(networkInfo.Ifces, ifce)\n}\n\nfunc GetNetworkInfo() *NetworkInfo {\n\tnetworkInfo := new(NetworkInfo)\n\tioStats, _ := net.IOCounters(true)\n\tr, _ := regexp.Compile(\"^(eth|enp).*\")\n\tfor _, ioStat := range ioStats {\n\t\tif !r.MatchString(ioStat.Name) {\n\t\t\tcontinue\n\t\t}\n\t\tifce := &Ifce{\n\t\t\tName:     ioStat.Name,\n\t\t\tByteSend: ioStat.BytesSent,\n\t\t\tByteRecv: ioStat.BytesRecv,\n\t\t}\n\t\tnetworkInfo.AppendIfce(ifce)\n\t}\n\treturn networkInfo\n}\n"
  },
  {
    "path": "page/backend/service/page_data.go",
    "content": "package service\n\nimport (\n\t\"time\"\n\n\t\"github.com/shirou/gopsutil/v3/cpu\"\n\t\"github.com/shirou/gopsutil/v3/disk\"\n\t\"github.com/shirou/gopsutil/v3/host\"\n\t\"github.com/shirou/gopsutil/v3/mem\"\n\t\"github.com/shirou/gopsutil/v3/net\"\n)\n\ntype PageData struct {\n\tUptime        uint64  `json:\"uptime\"`\n\tCPUPercent    float64 `json:\"cpu_percent\"`\n\tMemoryPercent float64 `json:\"memory_percent\"`\n\tDiskPercent   float64 `json:\"disk_percent\"`\n\tNetSendTotal  uint64  `json:\"net_send\"`\n\tNetRecvTotal  uint64  `json:\"net_recv\"`\n\tTimestamp     int64   `json:\"timestamp\"`\n}\n\nfunc GetPageData() *PageData {\n\thostInfo, _ := host.Info()\n\tcpuPercent, _ := cpu.Percent(time.Duration(1)*time.Second, false)\n\tmemInfo, _ := mem.VirtualMemory()\n\tdiskUsage, _ := disk.Usage(\"/\")\n\tioStats, _ := net.IOCounters(false)\n\treturn &PageData{\n\t\tUptime:        hostInfo.Uptime,\n\t\tCPUPercent:    cpuPercent[0],\n\t\tMemoryPercent: memInfo.UsedPercent,\n\t\tDiskPercent:   diskUsage.UsedPercent,\n\t\tNetSendTotal:  ioStats[0].BytesSent,\n\t\tNetRecvTotal:  ioStats[0].BytesRecv,\n\t\tTimestamp:     time.Now().Unix(),\n\t}\n}\n"
  },
  {
    "path": "page/backend/service/system.go",
    "content": "package service\n\nimport (\n\t\"time\"\n\n\t\"github.com/shirou/gopsutil/v3/cpu\"\n\t\"github.com/shirou/gopsutil/v3/load\"\n)\n\ntype SystemInfo struct {\n\tHostInfo     *HostInfo     `json:\"host_info\"`\n\tCPUInfo      *CPUInfo      `json:\"cpu_info\"`\n\tMemoryInfo   *MemoryInfo   `json:\"memory_info\"`\n\tNetworkInfo  *NetworkInfo  `json:\"network_info\"`\n\tDiskInfo     *DiskInfo     `json:\"disk_info\"`\n\tSystemStatus *SystemStatus `json:\"system_status\"`\n}\n\ntype SystemStatus struct {\n\tCPUPercent   []float64     `json:\"cpu_percent\"`\n\tLoadAvg      *load.AvgStat `json:\"load_avg\"`\n\tMemoryStatus *MemoryStatus `json:\"memory_status\"`\n\tDiskInfo     *DiskInfo     `json:\"disk_info\"`\n\tNetworkInfo  *NetworkInfo  `json:\"network_info\"`\n\tUptime       uint64        `json:\"uptime\"`\n\tTimestamp    int64         `json:\"timestamp\"`\n}\n\nfunc GetSystemInfo() *SystemInfo {\n\treturn &SystemInfo{\n\t\tHostInfo:     GetHostInfo(),\n\t\tCPUInfo:      GetCPUInfo(),\n\t\tMemoryInfo:   GetMemoryInfo(),\n\t\tDiskInfo:     GetDiskInfo(),\n\t\tNetworkInfo:  GetNetworkInfo(),\n\t\tSystemStatus: GetSystemStatus(),\n\t}\n}\n\nfunc GetSystemStatus() *SystemStatus {\n\tcpuPercent, _ := cpu.Percent(time.Duration(1)*time.Second, true)\n\tloadAvg, _ := load.Avg()\n\treturn &SystemStatus{\n\t\tCPUPercent:   cpuPercent,\n\t\tLoadAvg:      loadAvg,\n\t\tMemoryStatus: GetMemoryStatus(),\n\t\tDiskInfo:     GetDiskInfo(),\n\t\tNetworkInfo:  GetNetworkInfo(),\n\t\tUptime:       GetUptime(),\n\t\tTimestamp:    time.Now().Unix(),\n\t}\n}\n"
  },
  {
    "path": "page/frontend/.babelrc",
    "content": "{\n  \"presets\": [[\"es2015\", { \"modules\": false }]],\n  \"plugins\": [\n    [\n      \"component\",\n      {\n        \"libraryName\": \"element-ui\",\n        \"styleLibraryName\": \"theme-chalk\"\n      }\n    ]\n  ]\n}"
  },
  {
    "path": "page/frontend/.editorconfig",
    "content": "root = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\nend_of_line = lf\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n"
  },
  {
    "path": "page/frontend/.gitignore",
    "content": ".DS_Store\nnode_modules/\n/dist/\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# Editor directories and files\n.idea\n.vscode\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n"
  },
  {
    "path": "page/frontend/.postcssrc.js",
    "content": "// https://github.com/michael-ciniawsky/postcss-load-config\n\nmodule.exports = {\n  \"plugins\": {\n    \"postcss-import\": {},\n    \"postcss-url\": {},\n    // to edit target browsers: use \"browserslist\" field in package.json\n    \"autoprefixer\": {}\n  }\n}\n"
  },
  {
    "path": "page/frontend/README.md",
    "content": "# frontend\n\n> A Vue.js project\n\n## Build Setup\n\n``` bash\n# install dependencies\nnpm install\n\n# serve with hot reload at localhost:8080\nnpm run dev\n\n# build for production with minification\nnpm run build\n\n# build for production and view the bundle analyzer report\nnpm run build --report\n```\n\nFor a detailed explanation on how things work, check out the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader).\n"
  },
  {
    "path": "page/frontend/build/build.js",
    "content": "'use strict'\nrequire('./check-versions')()\n\nprocess.env.NODE_ENV = 'production'\n\nconst ora = require('ora')\nconst rm = require('rimraf')\nconst path = require('path')\nconst chalk = require('chalk')\nconst webpack = require('webpack')\nconst config = require('../config')\nconst webpackConfig = require('./webpack.prod.conf')\n\nconst spinner = ora('building for production...')\nspinner.start()\n\nrm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {\n  if (err) throw err\n  webpack(webpackConfig, (err, stats) => {\n    spinner.stop()\n    if (err) throw err\n    process.stdout.write(stats.toString({\n      colors: true,\n      modules: false,\n      children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build.\n      chunks: false,\n      chunkModules: false\n    }) + '\\n\\n')\n\n    if (stats.hasErrors()) {\n      console.log(chalk.red('  Build failed with errors.\\n'))\n      process.exit(1)\n    }\n\n    console.log(chalk.cyan('  Build complete.\\n'))\n    console.log(chalk.yellow(\n      '  Tip: built files are meant to be served over an HTTP server.\\n' +\n      '  Opening index.html over file:// won\\'t work.\\n'\n    ))\n  })\n})\n"
  },
  {
    "path": "page/frontend/build/check-versions.js",
    "content": "'use strict'\nconst chalk = require('chalk')\nconst semver = require('semver')\nconst packageConfig = require('../package.json')\nconst shell = require('shelljs')\n\nfunction exec (cmd) {\n  return require('child_process').execSync(cmd).toString().trim()\n}\n\nconst versionRequirements = [\n  {\n    name: 'node',\n    currentVersion: semver.clean(process.version),\n    versionRequirement: packageConfig.engines.node\n  }\n]\n\nif (shell.which('npm')) {\n  versionRequirements.push({\n    name: 'npm',\n    currentVersion: exec('npm --version'),\n    versionRequirement: packageConfig.engines.npm\n  })\n}\n\nmodule.exports = function () {\n  const warnings = []\n\n  for (let i = 0; i < versionRequirements.length; i++) {\n    const mod = versionRequirements[i]\n\n    if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {\n      warnings.push(mod.name + ': ' +\n        chalk.red(mod.currentVersion) + ' should be ' +\n        chalk.green(mod.versionRequirement)\n      )\n    }\n  }\n\n  if (warnings.length) {\n    console.log('')\n    console.log(chalk.yellow('To use this template, you must update following to modules:'))\n    console.log()\n\n    for (let i = 0; i < warnings.length; i++) {\n      const warning = warnings[i]\n      console.log('  ' + warning)\n    }\n\n    console.log()\n    process.exit(1)\n  }\n}\n"
  },
  {
    "path": "page/frontend/build/utils.js",
    "content": "'use strict'\nconst path = require('path')\nconst config = require('../config')\nconst ExtractTextPlugin = require('extract-text-webpack-plugin')\nconst packageConfig = require('../package.json')\n\nexports.assetsPath = function (_path) {\n  const assetsSubDirectory = process.env.NODE_ENV === 'production'\n    ? config.build.assetsSubDirectory\n    : config.dev.assetsSubDirectory\n\n  return path.posix.join(assetsSubDirectory, _path)\n}\n\nexports.cssLoaders = function (options) {\n  options = options || {}\n\n  const cssLoader = {\n    loader: 'css-loader',\n    options: {\n      sourceMap: options.sourceMap\n    }\n  }\n\n  const postcssLoader = {\n    loader: 'postcss-loader',\n    options: {\n      sourceMap: options.sourceMap\n    }\n  }\n\n  // generate loader string to be used with extract text plugin\n  function generateLoaders (loader, loaderOptions) {\n    const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader]\n\n    if (loader) {\n      loaders.push({\n        loader: loader + '-loader',\n        options: Object.assign({}, loaderOptions, {\n          sourceMap: options.sourceMap\n        })\n      })\n    }\n\n    // Extract CSS when that option is specified\n    // (which is the case during production build)\n    if (options.extract) {\n      return ExtractTextPlugin.extract({\n        use: loaders,\n        fallback: 'vue-style-loader'\n      })\n    } else {\n      return ['vue-style-loader'].concat(loaders)\n    }\n  }\n\n  // https://vue-loader.vuejs.org/en/configurations/extract-css.html\n  return {\n    css: generateLoaders(),\n    postcss: generateLoaders(),\n    less: generateLoaders('less'),\n    sass: generateLoaders('sass', { indentedSyntax: true }),\n    scss: generateLoaders('sass'),\n    stylus: generateLoaders('stylus'),\n    styl: generateLoaders('stylus')\n  }\n}\n\n// Generate loaders for standalone style files (outside of .vue)\nexports.styleLoaders = function (options) {\n  const output = []\n  const loaders = exports.cssLoaders(options)\n\n  for (const extension in loaders) {\n    const loader = loaders[extension]\n    output.push({\n      test: new RegExp('\\\\.' + extension + '$'),\n      use: loader\n    })\n  }\n\n  return output\n}\n\nexports.createNotifierCallback = () => {\n  const notifier = require('node-notifier')\n\n  return (severity, errors) => {\n    if (severity !== 'error') return\n\n    const error = errors[0]\n    const filename = error.file && error.file.split('!').pop()\n\n    notifier.notify({\n      title: packageConfig.name,\n      message: severity + ': ' + error.name,\n      subtitle: filename || '',\n      icon: path.join(__dirname, 'logo.png')\n    })\n  }\n}\n"
  },
  {
    "path": "page/frontend/build/vue-loader.conf.js",
    "content": "'use strict'\nconst utils = require('./utils')\nconst config = require('../config')\nconst isProduction = process.env.NODE_ENV === 'production'\nconst sourceMapEnabled = isProduction\n  ? config.build.productionSourceMap\n  : config.dev.cssSourceMap\n\nmodule.exports = {\n  loaders: utils.cssLoaders({\n    sourceMap: sourceMapEnabled,\n    extract: isProduction\n  }),\n  cssSourceMap: sourceMapEnabled,\n  cacheBusting: config.dev.cacheBusting,\n  transformToRequire: {\n    video: ['src', 'poster'],\n    source: 'src',\n    img: 'src',\n    image: 'xlink:href'\n  }\n}\n"
  },
  {
    "path": "page/frontend/build/webpack.base.conf.js",
    "content": "'use strict'\nconst path = require('path')\nconst utils = require('./utils')\nconst config = require('../config')\nconst vueLoaderConfig = require('./vue-loader.conf')\n\nfunction resolve (dir) {\n  return path.join(__dirname, '..', dir)\n}\n\n\n\nmodule.exports = {\n  context: path.resolve(__dirname, '../'),\n  entry: {\n    app: './src/main.js'\n  },\n  output: {\n    path: config.build.assetsRoot,\n    filename: '[name].js',\n    publicPath: process.env.NODE_ENV === 'production'\n      ? config.build.assetsPublicPath\n      : config.dev.assetsPublicPath\n  },\n  resolve: {\n    extensions: ['.js', '.vue', '.json'],\n    alias: {\n      'vue$': 'vue/dist/vue.esm.js',\n      '@': resolve('src'),\n    }\n  },\n  module: {\n    rules: [\n      {\n        test: /\\.vue$/,\n        loader: 'vue-loader',\n        options: vueLoaderConfig\n      },\n      {\n        test: /\\.js$/,\n        loader: 'babel-loader',\n        include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]\n      },\n      {\n        test: /\\.(png|jpe?g|gif|svg)(\\?.*)?$/,\n        loader: 'url-loader',\n        options: {\n          limit: 10000,\n          name: utils.assetsPath('img/[name].[hash:7].[ext]')\n        }\n      },\n      {\n        test: /\\.(mp4|webm|ogg|mp3|wav|flac|aac)(\\?.*)?$/,\n        loader: 'url-loader',\n        options: {\n          limit: 10000,\n          name: utils.assetsPath('media/[name].[hash:7].[ext]')\n        }\n      },\n      {\n        test: /\\.(woff2?|eot|ttf|otf)(\\?.*)?$/,\n        loader: 'url-loader',\n        options: {\n          limit: 10000,\n          name: utils.assetsPath('fonts/[name].[hash:7].[ext]')\n        }\n      }\n    ]\n  },\n  node: {\n    // prevent webpack from injecting useless setImmediate polyfill because Vue\n    // source contains it (although only uses it if it's native).\n    setImmediate: false,\n    // prevent webpack from injecting mocks to Node native modules\n    // that does not make sense for the client\n    dgram: 'empty',\n    fs: 'empty',\n    net: 'empty',\n    tls: 'empty',\n    child_process: 'empty'\n  }\n}\n"
  },
  {
    "path": "page/frontend/build/webpack.dev.conf.js",
    "content": "'use strict'\nconst utils = require('./utils')\nconst webpack = require('webpack')\nconst config = require('../config')\nconst merge = require('webpack-merge')\nconst path = require('path')\nconst baseWebpackConfig = require('./webpack.base.conf')\nconst CopyWebpackPlugin = require('copy-webpack-plugin')\nconst HtmlWebpackPlugin = require('html-webpack-plugin')\nconst FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')\nconst portfinder = require('portfinder')\n\nconst HOST = process.env.HOST\nconst PORT = process.env.PORT && Number(process.env.PORT)\n\nconst devWebpackConfig = merge(baseWebpackConfig, {\n  module: {\n    rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true })\n  },\n  // cheap-module-eval-source-map is faster for development\n  devtool: config.dev.devtool,\n\n  // these devServer options should be customized in /config/index.js\n  devServer: {\n    clientLogLevel: 'warning',\n    historyApiFallback: {\n      rewrites: [\n        { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') },\n      ],\n    },\n    hot: true,\n    contentBase: false, // since we use CopyWebpackPlugin.\n    compress: true,\n    host: HOST || config.dev.host,\n    port: PORT || config.dev.port,\n    open: config.dev.autoOpenBrowser,\n    overlay: config.dev.errorOverlay\n      ? { warnings: false, errors: true }\n      : false,\n    publicPath: config.dev.assetsPublicPath,\n    proxy: config.dev.proxyTable,\n    quiet: true, // necessary for FriendlyErrorsPlugin\n    watchOptions: {\n      poll: config.dev.poll,\n    }\n  },\n  plugins: [\n    new webpack.DefinePlugin({\n      'process.env': require('../config/dev.env')\n    }),\n    new webpack.HotModuleReplacementPlugin(),\n    new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update.\n    new webpack.NoEmitOnErrorsPlugin(),\n    // https://github.com/ampedandwired/html-webpack-plugin\n    new HtmlWebpackPlugin({\n      filename: 'index.html',\n      template: 'index.html',\n      inject: true\n    }),\n    // copy custom static assets\n    new CopyWebpackPlugin([\n      {\n        from: path.resolve(__dirname, '../static'),\n        to: config.dev.assetsSubDirectory,\n        ignore: ['.*']\n      }\n    ])\n  ]\n})\n\nmodule.exports = new Promise((resolve, reject) => {\n  portfinder.basePort = process.env.PORT || config.dev.port\n  portfinder.getPort((err, port) => {\n    if (err) {\n      reject(err)\n    } else {\n      // publish the new Port, necessary for e2e tests\n      process.env.PORT = port\n      // add port to devServer config\n      devWebpackConfig.devServer.port = port\n\n      // Add FriendlyErrorsPlugin\n      devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({\n        compilationSuccessInfo: {\n          messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`],\n        },\n        onErrors: config.dev.notifyOnErrors\n        ? utils.createNotifierCallback()\n        : undefined\n      }))\n\n      resolve(devWebpackConfig)\n    }\n  })\n})\n"
  },
  {
    "path": "page/frontend/build/webpack.prod.conf.js",
    "content": "'use strict'\nconst path = require('path')\nconst utils = require('./utils')\nconst webpack = require('webpack')\nconst config = require('../config')\nconst merge = require('webpack-merge')\nconst baseWebpackConfig = require('./webpack.base.conf')\nconst CopyWebpackPlugin = require('copy-webpack-plugin')\nconst HtmlWebpackPlugin = require('html-webpack-plugin')\nconst ExtractTextPlugin = require('extract-text-webpack-plugin')\nconst OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')\nconst UglifyJsPlugin = require('uglifyjs-webpack-plugin')\n\nconst env = require('../config/prod.env')\n\nconst webpackConfig = merge(baseWebpackConfig, {\n  module: {\n    rules: utils.styleLoaders({\n      sourceMap: config.build.productionSourceMap,\n      extract: true,\n      usePostCSS: true\n    })\n  },\n  devtool: config.build.productionSourceMap ? config.build.devtool : false,\n  output: {\n    path: config.build.assetsRoot,\n    filename: utils.assetsPath('js/[name].[chunkhash].js'),\n    chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')\n  },\n  plugins: [\n    // http://vuejs.github.io/vue-loader/en/workflow/production.html\n    new webpack.DefinePlugin({\n      'process.env': env\n    }),\n    new UglifyJsPlugin({\n      uglifyOptions: {\n        compress: {\n          warnings: false\n        }\n      },\n      sourceMap: config.build.productionSourceMap,\n      parallel: true\n    }),\n    // extract css into its own file\n    new ExtractTextPlugin({\n      filename: utils.assetsPath('css/[name].[contenthash].css'),\n      // Setting the following option to `false` will not extract CSS from codesplit chunks.\n      // Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack.\n      // It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`, \n      // increasing file size: https://github.com/vuejs-templates/webpack/issues/1110\n      allChunks: true,\n    }),\n    // Compress extracted CSS. We are using this plugin so that possible\n    // duplicated CSS from different components can be deduped.\n    new OptimizeCSSPlugin({\n      cssProcessorOptions: config.build.productionSourceMap\n        ? { safe: true, map: { inline: false } }\n        : { safe: true }\n    }),\n    // generate dist index.html with correct asset hash for caching.\n    // you can customize output by editing /index.html\n    // see https://github.com/ampedandwired/html-webpack-plugin\n    new HtmlWebpackPlugin({\n      filename: config.build.index,\n      template: 'index.html',\n      inject: true,\n      minify: {\n        removeComments: true,\n        collapseWhitespace: true,\n        removeAttributeQuotes: true\n        // more options:\n        // https://github.com/kangax/html-minifier#options-quick-reference\n      },\n      // necessary to consistently work with multiple chunks via CommonsChunkPlugin\n      chunksSortMode: 'dependency'\n    }),\n    // keep module.id stable when vendor modules does not change\n    new webpack.HashedModuleIdsPlugin(),\n    // enable scope hoisting\n    new webpack.optimize.ModuleConcatenationPlugin(),\n    // split vendor js into its own file\n    new webpack.optimize.CommonsChunkPlugin({\n      name: 'vendor',\n      minChunks (module) {\n        // any required modules inside node_modules are extracted to vendor\n        return (\n          module.resource &&\n          /\\.js$/.test(module.resource) &&\n          module.resource.indexOf(\n            path.join(__dirname, '../node_modules')\n          ) === 0\n        )\n      }\n    }),\n    // extract webpack runtime and module manifest to its own file in order to\n    // prevent vendor hash from being updated whenever app bundle is updated\n    new webpack.optimize.CommonsChunkPlugin({\n      name: 'manifest',\n      minChunks: Infinity\n    }),\n    // This instance extracts shared chunks from code splitted chunks and bundles them\n    // in a separate chunk, similar to the vendor chunk\n    // see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk\n    new webpack.optimize.CommonsChunkPlugin({\n      name: 'app',\n      async: 'vendor-async',\n      children: true,\n      minChunks: 3\n    }),\n\n    // copy custom static assets\n    new CopyWebpackPlugin([\n      {\n        from: path.resolve(__dirname, '../static'),\n        to: config.build.assetsSubDirectory,\n        ignore: ['.*']\n      }\n    ])\n  ]\n})\n\nif (config.build.productionGzip) {\n  const CompressionWebpackPlugin = require('compression-webpack-plugin')\n\n  webpackConfig.plugins.push(\n    new CompressionWebpackPlugin({\n      asset: '[path].gz[query]',\n      algorithm: 'gzip',\n      test: new RegExp(\n        '\\\\.(' +\n        config.build.productionGzipExtensions.join('|') +\n        ')$'\n      ),\n      threshold: 10240,\n      minRatio: 0.8\n    })\n  )\n}\n\nif (config.build.bundleAnalyzerReport) {\n  const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin\n  webpackConfig.plugins.push(new BundleAnalyzerPlugin())\n}\n\nmodule.exports = webpackConfig\n"
  },
  {
    "path": "page/frontend/config/dev.env.js",
    "content": "'use strict'\nconst merge = require('webpack-merge')\nconst prodEnv = require('./prod.env')\n\nmodule.exports = merge(prodEnv, {\n  NODE_ENV: '\"development\"',\n})\n"
  },
  {
    "path": "page/frontend/config/index.js",
    "content": "'use strict'\n// Template version: 1.3.1\n// see http://vuejs-templates.github.io/webpack for documentation.\n\nconst path = require('path')\n\nmodule.exports = {\n  dev: {\n\n    // Paths\n    assetsSubDirectory: 'static',\n    assetsPublicPath: './',\n    proxyTable: {},\n\n    // Various Dev Server settings\n    host: 'localhost', // can be overwritten by process.env.HOST\n    port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined\n    autoOpenBrowser: false,\n    errorOverlay: true,\n    notifyOnErrors: true,\n    poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-\n\n    \n    /**\n     * Source Maps\n     */\n\n    // https://webpack.js.org/configuration/devtool/#development\n    devtool: 'cheap-module-eval-source-map',\n\n    // If you have problems debugging vue-files in devtools,\n    // set this to false - it *may* help\n    // https://vue-loader.vuejs.org/en/options.html#cachebusting\n    cacheBusting: true,\n\n    cssSourceMap: true\n  },\n\n  build: {\n    // Template for index.html\n    index: path.resolve(__dirname, '../dist/index.html'),\n\n    // Paths\n    assetsRoot: path.resolve(__dirname, '../dist'),\n    assetsSubDirectory: 'static',\n    assetsPublicPath: './',\n\n    /**\n     * Source Maps\n     */\n\n    productionSourceMap: true,\n    // https://webpack.js.org/configuration/devtool/#production\n    devtool: '#source-map',\n\n    // Gzip off by default as many popular static hosts such as\n    // Surge or Netlify already gzip all static assets for you.\n    // Before setting to `true`, make sure to:\n    // npm install --save-dev compression-webpack-plugin\n    productionGzip: false,\n    productionGzipExtensions: ['js', 'css'],\n\n    // Run the build command with an extra argument to\n    // View the bundle analyzer report after build finishes:\n    // `npm run build --report`\n    // Set to `true` or `false` to always turn it on or off\n    bundleAnalyzerReport: process.env.npm_config_report\n  }\n}\n"
  },
  {
    "path": "page/frontend/config/prod.env.js",
    "content": "'use strict'\nmodule.exports = {\n  NODE_ENV: '\"production\"'\n}\n"
  },
  {
    "path": "page/frontend/index.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" id=\"viewport\" content=\"width=device-width,initial-scale=1.0, user-scalable=yes\">\n    <title>Moyu Page</title>\n  </head>\n  <body>\n    <div id=\"app\"></div>\n    <!-- built files will be auto injected -->\n  </body>\n  <script>\n     let ua = navigator.userAgent;\n     let ipad = ua.match(/(iPad).*OS\\s([\\d_]+)/);\n     let isIphone = !ipad && ua.match(/(iPhone\\sOS)\\s([\\d_]+)/);\n     let isAndroid = ua.match(/(Android)\\s+([\\d.]+)/);\n     isMobile = isIphone || isAndroid;\n    if (isMobile) {\n      document.getElementById(\"viewport\").setAttribute(\"content\", \"user-scalable=yes, width=device-width, initial-scale=\" + 0.4);\n    }\n  </script>\n</html>\n"
  },
  {
    "path": "page/frontend/package.json",
    "content": "{\n  \"name\": \"frontend\",\n  \"version\": \"1.0.0\",\n  \"description\": \"A Vue.js project\",\n  \"author\": \"liuxu\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"webpack-dev-server --inline --progress --config build/webpack.dev.conf.js\",\n    \"start\": \"npm run dev\",\n    \"build\": \"node build/build.js\",\n    \"postbuild\": \"rm -rf ../backend/dist && mv dist ../backend/\"\n  },\n  \"dependencies\": {\n    \"axios\": \"^1.1.3\",\n    \"element-ui\": \"^2.15.12\",\n    \"modern-normalize\": \"^1.1.0\",\n    \"nes.css\": \"^2.3.0\",\n    \"vue\": \"^2.5.2\",\n    \"vue-router\": \"^3.0.1\"\n  },\n  \"devDependencies\": {\n    \"autoprefixer\": \"^7.1.2\",\n    \"babel-core\": \"^6.22.1\",\n    \"babel-helper-vue-jsx-merge-props\": \"^2.0.3\",\n    \"babel-loader\": \"^7.1.1\",\n    \"babel-plugin-component\": \"^1.1.1\",\n    \"babel-plugin-syntax-jsx\": \"^6.18.0\",\n    \"babel-plugin-transform-runtime\": \"^6.22.0\",\n    \"babel-plugin-transform-vue-jsx\": \"^3.5.0\",\n    \"babel-preset-env\": \"^1.3.2\",\n    \"babel-preset-es2015\": \"^6.24.1\",\n    \"babel-preset-stage-2\": \"^6.22.0\",\n    \"chalk\": \"^2.0.1\",\n    \"copy-webpack-plugin\": \"^4.0.1\",\n    \"extract-text-webpack-plugin\": \"^3.0.0\",\n    \"file-loader\": \"^1.1.4\",\n    \"friendly-errors-webpack-plugin\": \"^1.6.1\",\n    \"html-webpack-plugin\": \"^2.30.1\",\n    \"node-notifier\": \"^5.1.2\",\n    \"optimize-css-assets-webpack-plugin\": \"^3.2.0\",\n    \"ora\": \"^1.2.0\",\n    \"portfinder\": \"^1.0.13\",\n    \"postcss-import\": \"^11.0.0\",\n    \"postcss-loader\": \"^2.0.8\",\n    \"postcss-url\": \"^7.2.1\",\n    \"rimraf\": \"^2.6.0\",\n    \"semver\": \"^5.3.0\",\n    \"shelljs\": \"^0.7.6\",\n    \"uglifyjs-webpack-plugin\": \"^1.1.1\",\n    \"url-loader\": \"^0.5.8\",\n    \"vue-loader\": \"^13.3.0\",\n    \"vue-template-compiler\": \"^2.5.2\",\n    \"webpack\": \"^3.6.0\",\n    \"webpack-bundle-analyzer\": \"^2.9.0\",\n    \"webpack-dev-server\": \"^2.9.1\",\n    \"webpack-merge\": \"^4.1.0\"\n  },\n  \"engines\": {\n    \"node\": \">= 6.0.0\",\n    \"npm\": \">= 3.0.0\"\n  },\n  \"browserslist\": [\n    \"> 1%\",\n    \"last 2 versions\",\n    \"not ie <= 8\"\n  ]\n}\n"
  },
  {
    "path": "page/frontend/src/App.vue",
    "content": "<template>\n  <div id=\"app\">\n    <router-view/>\n  </div>\n</template>\n\n<script>\nexport default {\n  name: 'App',\n  watch: {\n        $route: {\n            immediate: true,\n            handler(to, from) {\n                document.title = to.meta.title || 'MoYu';\n            }\n        },\n    }\n}\n</script>\n\n<style>\n@import url('https://fonts.googleapis.com/css?family=Press+Start+2P');\n#app {\n  background: #FFFFFA;\n}\n</style>\n"
  },
  {
    "path": "page/frontend/src/common/filters.js",
    "content": "/**\n * 转换成可读字节\n */\nexport function humanByte(size) {\n    if (size < 1024) {\n        return size + \" Byte\"\n    } else if (size < 1024 * 1024) {\n        return Math.trunc(size / 1024) + \" KB\"\n    } else if (size < 1024 * 1024 * 1024) {\n        return (size / 1024 / 1024).toFixed(2) + \" MB\"\n    } else {\n        return (size / 1024 / 1024 / 1024).toFixed(2) + \" GB\"\n    }\n}\n\n/**\n * 将秒数转换成可读日时分秒\n * 例：3600秒->1小时\n * 例：183秒->3分3秒\n */\nexport function humanSec(sec) {\n    if (sec < 60) {\n        return Math.trunc(sec) + \" seconds\";\n    } else if (sec < 3600) {\n        var sec = sec % 60\n        var min = Math.trunc(sec / 60);\n        return min + \" minutes \" + sec + \" seconds\"\n    } else if (sec < 86400) {\n        var min = Math.trunc(sec % 3600 / 60);\n        var hour = Math.trunc(sec / 3600);\n        return hour + \" hours \" + min + \" minutes\"\n    } else {\n        var hour = Math.trunc(sec % 86400 / 3600);\n        var day = Math.trunc(sec / 86400);\n        return day + \" days \" + hour + \" hours\"\n    }\n}\n\n/**\n * 转换为num位百分比\n */\nexport function humanPerc(float, num) {\n    if (!float || !num) {\n        return 0\n    }\n    return float.toFixed(num)\n}"
  },
  {
    "path": "page/frontend/src/components/Cpu.vue",
    "content": "<template>\n    <div>\n        <el-row v-for=\"(percent, index) in cpu_percent\" :key=\"index\">\n            <el-col>\n                <progress class=\"nes-progress is-success\" :value=\"percent\" max=\"100\"></progress>\n            </el-col>\n        </el-row>\n    </div>\n</template>\n\n<script>\n    export default {\n        name: \"cpu\",\n        props: ['cpu_percent'],\n    }\n</script>\n"
  },
  {
    "path": "page/frontend/src/components/Disk.vue",
    "content": "<template>\n    <div>\n        <el-row v-for=\"partition in partitions\" :key=\"partition.device\">\n            <el-col>\n                <span>{{ partition.device }}({{ partition.size | humanByte }}) | Used({{ usedDisk(partition.size, partition.used_percent) | humanByte }}) | Mounted({{ partition.mount_point }}):</span>\n            </el-col>\n            <el-col>\n                <progress class=\"nes-progress is-primary\" :value=\"partition.used_percent\" max=\"100\"></progress>\n            </el-col>\n        </el-row>\n    </div>\n</template>\n\n<script>\n    import {humanByte, humanPerc} from \"@/common/filters\"\n    export default {\n        name: \"meminfo\",\n        props: ['partitions'],\n        filters: {\n            humanByte: function(size) {\n                return humanByte(size)\n            },\n            humanPerc: function(float, num) {\n                return humanPerc(float, num)\n            },\n        },\n        methods: {\n            usedDisk(diskSize, percent) {\n                return diskSize * percent / 100;\n            },\n        }\n    }\n</script>"
  },
  {
    "path": "page/frontend/src/components/Host.vue",
    "content": "<template>\n    <div>\n        <el-row>\n            <el-col :xs=\"7\" :sm=\"7\" :md=\"5\" :lg=\"4\" :xl=\"4\"><span>Hostname:</span></el-col>\n            <el-col :xs=\"17\" :sm=\"17\" :md=\"19\" :lg=\"20\" :xl=\"20\"><span>{{ info.hostname }}</span></el-col>\n        </el-row>\n        <el-row>\n            <el-col :xs=\"7\" :sm=\"7\" :md=\"5\" :lg=\"4\" :xl=\"4\"><span>OS:</span></el-col>\n            <el-col :xs=\"17\" :sm=\"17\" :md=\"19\" :lg=\"20\" :xl=\"20\"><span>{{ info.os }}</span></el-col>\n        </el-row>\n        <el-row>\n            <el-col :xs=\"7\" :sm=\"7\" :md=\"5\" :lg=\"4\" :xl=\"4\"><span>CPU:</span></el-col>\n            <el-col :xs=\"17\" :sm=\"17\" :md=\"19\" :lg=\"20\" :xl=\"20\"><span>{{ info.cpu }}</span></el-col>\n        </el-row>\n        <el-row>\n            <el-col :xs=\"7\" :sm=\"7\" :md=\"5\" :lg=\"4\" :xl=\"4\"><span>AES-NI:</span></el-col>\n            <el-col :xs=\"17\" :sm=\"17\" :md=\"19\" :lg=\"20\" :xl=\"20\"><span>{{ info.aes_ni }}</span></el-col>\n        </el-row>\n        <el-row>\n            <el-col :xs=\"7\" :sm=\"7\" :md=\"5\" :lg=\"4\" :xl=\"4\"><span>VM-x/AMD-V:</span></el-col>\n            <el-col :xs=\"17\" :sm=\"17\" :md=\"19\" :lg=\"20\" :xl=\"20\"><span>{{ info.vm_x_amd_v }}</span></el-col>\n        </el-row>\n        <el-row>\n            <el-col :xs=\"7\" :sm=\"7\" :md=\"5\" :lg=\"4\" :xl=\"4\"><span>Virt:</span></el-col>\n            <el-col :xs=\"17\" :sm=\"17\" :md=\"19\" :lg=\"20\" :xl=\"20\"><span>{{ info.virt }}</span></el-col>\n        </el-row>\n        <el-row>\n            <el-col :xs=\"7\" :sm=\"7\" :md=\"5\" :lg=\"4\" :xl=\"4\"><span>Uptime:</span></el-col>\n            <el-col :xs=\"17\" :sm=\"17\" :md=\"19\" :lg=\"20\" :xl=\"20\"><span>{{ info.uptime | humanSec }}</span></el-col>\n        </el-row>\n        <el-row>\n            <el-col :xs=\"7\" :sm=\"7\" :md=\"5\" :lg=\"4\" :xl=\"4\"><span>Load:</span></el-col>\n            <el-col :xs=\"6\" :sm=\"6\" :md=\"5\" :lg=\"4\" :xl=\"4\">\n                <div class=\"nes-badge is-splited\">\n                    <span class=\"is-dark\">1min</span>\n                    <span class=\"is-warning\">{{ info.load_avg.load1 }}</span>\n                </div>\n            </el-col>\n            <el-col :xs=\"6\" :sm=\"6\" :md=\"5\" :lg=\"4\" :xl=\"4\">\n                <div class=\"nes-badge is-splited\">\n                    <span class=\"is-dark\">5min</span>\n                    <span class=\"is-warning\">{{ info.load_avg.load5 }}</span>\n                </div>\n            </el-col>\n            <el-col :xs=\"5\" :sm=\"5\" :md=\"5\" :lg=\"4\" :xl=\"4\">\n                <div class=\"nes-badge is-splited\">\n                    <span class=\"is-dark\">15min</span>\n                    <span class=\"is-warning\">{{ info.load_avg.load15 }}</span>\n                </div>\n            </el-col>\n        </el-row>\n        <el-row>\n            <el-col :xs=\"7\" :sm=\"7\" :md=\"5\" :lg=\"4\" :xl=\"4\"><span>BW(u|d):</span></el-col>\n            <el-col :xs=\"9\" :sm=\"10\" :md=\"9\" :lg=\"8\" :xl=\"8\" v-for=\"ifce in info.ifces\" :key=\"ifce.name\">\n                <div class=\"nes-badge is-splited net-badge\">\n                    <span class=\"is-dark\">{{ curNetSend(ifce.send_byte) | humanByte }}/s</span>\n                    <span class=\"is-warning\">{{ curNetRecv(ifce.recv_byte) | humanByte  }}/s</span>\n                </div>\n            </el-col>\n        </el-row>\n        <el-row>\n            <el-col :xs=\"7\" :sm=\"7\" :md=\"5\" :lg=\"4\" :xl=\"4\"><span>Traffic:</span></el-col>\n            <el-col :xs=\"9\" :sm=\"10\" :md=\"9\" :lg=\"8\" :xl=\"8\" v-for=\"ifce in info.ifces\" :key=\"ifce.name\">\n                <div class=\"nes-badge is-splited net-badge\">\n                    <span class=\"is-dark\">{{ ifce.send_byte | humanByte }}</span>\n                    <span class=\"is-warning\">{{ ifce.recv_byte | humanByte  }}</span>\n                </div>\n            </el-col>\n        </el-row>\n    </div>\n</template>\n\n<script>\n    import {humanSec, humanByte} from \"@/common/filters\"\n    export default {\n        name: \"host\",\n        props: ['info'],\n        methods: {\n            curNetSend: function(curTotalSend) {\n                var lastTotalSend = sessionStorage.getItem(\"lastTotalSend\")\n                if (!lastTotalSend) {\n                    lastTotalSend = curTotalSend\n                }\n                sessionStorage.setItem(\"lastTotalSend\", curTotalSend)\n                return curTotalSend - lastTotalSend\n            },\n            curNetRecv: function(curTotalRecv) {\n                var lastTotalRecv = sessionStorage.getItem(\"lastTotalRecv\")\n                if (!lastTotalRecv) {\n                    lastTotalRecv = curTotalRecv\n                }\n                sessionStorage.setItem(\"lastTotalRecv\", curTotalRecv)\n                return curTotalRecv - lastTotalRecv\n            }\n        },\n        filters: {\n            humanSec: function(sec) {\n                return humanSec(sec)\n            },\n            humanByte: function(size) {\n                return humanByte(size)\n            }\n        },\n    }\n</script>\n<style scoped>\n.el-row {\n    margin-top: 5px;\n}\n.net-badge {\n    width: 330px;\n}\n</style>\n"
  },
  {
    "path": "page/frontend/src/components/Mem.vue",
    "content": "<template>\n    <div>\n        <el-row>\n            <el-col>\n                <span>Memory({{ info.memory | humanByte }}) | Used({{ usedMem | humanByte }}):</span>\n            </el-col>\n            <el-col>\n                <progress class=\"nes-progress is-error\" :value=\"status.memory_percent\" max=\"100\"></progress>\n            </el-col>\n        </el-row>\n        <el-row>\n            <el-col>\n                <span>Swap({{ info.swap | humanByte }}) | Used({{ usedSwap | humanByte }}):</span>\n            </el-col>\n            <el-col>\n                <progress class=\"nes-progress is-error\" :value=\"status.swap_percent\" max=\"100\"></progress>\n            </el-col>\n        </el-row>\n    </div>\n</template>\n\n<script>\n    import {humanByte, humanPerc} from \"@/common/filters\"\n    export default {\n        name: \"mee\",\n        props: ['info', 'status'],\n        filters: {\n            humanByte: function(size) {\n                return humanByte(size)\n            },\n            humanPerc: function(float, num) {\n                return humanPerc(float, num)\n            }\n        },\n        computed: {\n            usedMem() {\n                return this.info.memory * this.status.memory_percent / 100;\n            },\n            usedSwap() {\n                return this.info.swap * this.status.swap_percent / 100;\n            },\n        }\n    }\n</script>"
  },
  {
    "path": "page/frontend/src/layouts/Main.vue",
    "content": "<template>\n  <div class=\"container\">\n    <slot></slot>\n  </div>\n</template>\n\n<script>\n</script>\n\n<style scoped>\n.container {\n    max-width: 1400px;\n    margin: 0 auto;\n    padding: 15px 30px;\n}\n</style>"
  },
  {
    "path": "page/frontend/src/main.js",
    "content": "// The Vue build version to load with the `import` command\n// (runtime-only or standalone) has been set in webpack.base.conf with an alias.\nimport Vue from 'vue'\nimport App from './App'\nimport router from './router'\n\nimport \"modern-normalize/modern-normalize.css\"\nimport {\n  Row,\n  Col,\n  Container,\n  Header,\n  Main,\n  Footer,\n} from 'element-ui'\nimport 'element-ui/lib/theme-chalk/index.css'\nimport axios from 'axios'\nimport \"nes.css/css/nes.min.css\"\n\nVue.config.productionTip = false\nVue.prototype.axios = axios\n\nVue.component(Row.name, Row)\nVue.component(Col.name, Col)\nVue.component(Container.name, Container)\nVue.component(Header.name, Header)\nVue.component(Main.name, Main)\nVue.component(Footer.name, Footer)\n\n/* eslint-disable no-new */\nnew Vue({\n  el: '#app',\n  router,\n  components: { App },\n  template: '<App/>'\n})\n"
  },
  {
    "path": "page/frontend/src/pages/SystemPage.vue",
    "content": "<template>\n    <el-container class=\"content-container\">\n        <el-header height=\"80px\">\n            <div class=\"logo\">\n                <span>MoYu</span>\n            </div>\n            <div class=\"github-logo\">\n                <a href=\"https://github.com/liuquanhao/moyu\" target=\"_blank\" rel=\"noopener\">\n                    <i class=\"nes-icon github is-large\"></i>\n                </a>\n            </div>\n        </el-header>\n        <el-main>\n            <div class=\"nes-container with-title\">\n                <p class=\"title\">Basic System Information</p>\n                <host :info=\"host_data\"/>\n                <div class=\"nes-container with-title is-centered component-container\">\n                    <p class=\"title\">CPU</p>\n                    <cpu :cpu_percent=\"cpu_percent\" />\n                </div>\n                <div class=\"nes-container with-title is-centered component-container\">\n                    <p class=\"title\">Memory</p>\n                    <mem :info=\"memory_info\" :status=\"memory_status\" />\n                </div>\n                <div class=\"nes-container with-title is-centered component-container\">\n                    <p class=\"title\">Disk</p>\n                    <disk :partitions=\"partitions\" />\n                </div>\n            </div>\n        </el-main>\n        <el-footer>\n            <p class=\"powered-by\">Powered By: liuxu</p>\n        </el-footer>\n    </el-container>\n</template>\n  \n<script>\n    import MainLayout from '../layouts/Main.vue'\n    import Host from '../components/Host.vue'\n    import Cpu from '../components/Cpu.vue'\n    import Mem from '../components/Mem.vue'\n    import Disk from '../components/Disk.vue'\n    export default {\n        components: {\n            MainLayout,\n            Host,\n            Cpu,\n            Mem,\n            Disk,\n        },\n        data: function() {\n            return {\n                ws: null,\n                host_info: {},\n                cpu_info: {\n                    \"count\": 0,\n                    \"cores\": [],\n                },\n                cpu_percent: {},\n                load_avg: {},\n                memory_info: {},\n                memory_status: {},\n                ifces: {},\n                partitions: {},\n                uptime: {},\n                timestamp: 0,\n            }\n        },\n        computed: {\n            host_data: function() {\n                let hostname = this.host_info.hostname\n                let os = this.host_info.distribution + \" (\" + this.host_info.kernel + \" \" + this.host_info.arch + \")\"\n                let cpu = 'unknown'\n                let aes_ni = false\n                let vm_x_amd_v = false\n                if (this.cpu_info.cores.length > 0) {\n                    cpu = this.cpu_info.cores[0].model + \" @ \" + this.cpu_info.cores[0].mhz\n                    aes_ni = this.cpu_info.cores[0].flags.includes(\"aes\")\n                    vm_x_amd_v = this.cpu_info.cores[0].flags.includes(\"vmx\") || this.cpu_info.cores[0].flags.includes(\"svm\")\n                }\n                let virt = this.host_info.virtual_platform ? this.host_info.virtual_platform : 'unknown'\n                let uptime = this.host_info.uptime\n                let load_avg = this.load_avg\n                let ifces = this.ifces\n                return {\n                    hostname,\n                    os,\n                    cpu,\n                    aes_ni,\n                    vm_x_amd_v,\n                    virt,\n                    uptime,\n                    load_avg,\n                    ifces,\n                }\n            },\n        },\n        created() {\n            this.initData()\n            this.initWs()\n        },\n        destroyed() {\n            this.ws.close()\n        },\n        methods: {\n            initData() {\n\n                this.axios.get(\"./api/sys_info\").then(\n                    res => {\n                        this.host_info = res.data.host_info\n                        this.cpu_info = res.data.cpu_info\n                        this.load_avg = res.data.system_status.load_avg\n                        this.memory_info = res.data.memory_info\n                        this.ifces = res.data.network_info.ifces\n                        this.partitions = res.data.disk_info.partitions\n                        this.cpu_percent = res.data.system_status.cpu_percent\n                        this.memory_status = res.data.system_status.memory_status\n                        this.timestamp = res.data.timestamp\n                    }\n                ).catch(res => {\n                    console.log(res)\n                })\n            },\n            initWs() {\n                let wsProtocol = window.location.protocol == \"https:\" ? \"wss://\" : \"ws://\";\n                let wsPort = window.location.port == \"\" ? \"\" : \":\" + window.location.port;\n                this.ws = new WebSocket(wsProtocol + window.location.hostname + wsPort + window.location.pathname + \"ws/sys_status\")\n                this.ws.onopen = this.wsOnOpen\n                this.ws.onerror = this.wsOnError\n                this.ws.onmessage = this.wsOnMessage\n                this.ws.onclose = this.wsOnClose\n            },\n            wsOnOpen() {\n                console.log(\"ws连接成功\")\n            },\n            wsOnError() {\n                console.log(\"ws连接错误\")\n                this.initWs()\n            },\n            wsOnMessage(e) {\n                var systemStatus = JSON.parse(e.data)\n                this.cpu_percent = systemStatus.cpu_percent\n                this.load_avg = systemStatus.load_avg\n                this.memory_status = systemStatus.memory_status\n                this.ifces = systemStatus.network_info.ifces\n                this.partitions = systemStatus.disk_info.partitions\n                this.uptime = systemStatus.uptime\n            },\n            wsOnClose(e) {\n                console.log(\"ws断开连接\")\n            }\n        }\n    }\n</script>\n\n<style scoped>\n.logo {\n    margin-right: auto;\n    margin-left: 10px;\n    font-size: 50px;\n    height: 10px;\n}\n.github-logo {\n    height: 10px;\n    margin-right: 10px;\n}\n.content-container {\n    min-width: 780px;\n    max-width: 1260px;\n    margin: 10px auto;\n    justify-content: center;\n}\n.el-header {\n    left: 0;\n    right: 0;\n    display: flex;\n    flex-direction: row;\n    border-bottom: 4px solid #D3D3D3; \n}\n.nes-octocat {\n    height: 30px;\n}\n.component-container {\n    margin-top: 20px;\n}\n.el-footer {\n    margin-top: 10px;\n    margin-left: auto;\n}\n.powered-by {\n    margin-right: 10px;\n}\n</style>"
  },
  {
    "path": "page/frontend/src/router/index.js",
    "content": "import Vue from 'vue'\nimport Router from 'vue-router'\nimport SystemPage from '@/pages/SystemPage'\n\nVue.use(Router)\n\nexport default new Router({\n  routes: [\n    {\n      path: '/',\n      name: 'SystemPage',\n      component: SystemPage\n    }\n  ]\n})\n"
  },
  {
    "path": "page/frontend/static/.gitkeep",
    "content": ""
  }
]