Full Code of liuquanhao/moyu for AI

main 880e71a214fe cached
89 files
125.3 KB
41.9k tokens
69 symbols
1 requests
Download .txt
Repository: liuquanhao/moyu
Branch: main
Commit: 880e71a214fe
Files: 89
Total size: 125.3 KB

Directory structure:
gitextract_ebjt9uy3/

├── .github/
│   └── workflows/
│       └── release.yml
├── .gitignore
├── Makefile
├── ManagerDockerfile
├── PageDockerfile
├── README.md
├── manager/
│   ├── backend/
│   │   ├── .gitignore
│   │   ├── controller/
│   │   │   ├── page_data_controller/
│   │   │   │   └── page_data_controller.go
│   │   │   ├── page_url_controller/
│   │   │   │   └── page_url_controller.go
│   │   │   └── user_controller/
│   │   │       └── user_controller.go
│   │   ├── database/
│   │   │   └── init.sql
│   │   ├── go.mod
│   │   ├── go.sum
│   │   ├── main.go
│   │   ├── middleware/
│   │   │   ├── logged.go
│   │   │   └── ws_options.go
│   │   └── model/
│   │       ├── db.go
│   │       ├── remote_url_model/
│   │       │   └── remote_url_model.go
│   │       ├── session.go
│   │       └── user_model/
│   │           └── user_model.go
│   └── frontend/
│       ├── .babelrc
│       ├── .editorconfig
│       ├── .gitignore
│       ├── .postcssrc.js
│       ├── README.md
│       ├── build/
│       │   ├── build.js
│       │   ├── check-versions.js
│       │   ├── utils.js
│       │   ├── vue-loader.conf.js
│       │   ├── webpack.base.conf.js
│       │   ├── webpack.dev.conf.js
│       │   └── webpack.prod.conf.js
│       ├── config/
│       │   ├── dev.env.js
│       │   ├── index.js
│       │   └── prod.env.js
│       ├── index.html
│       ├── package.json
│       ├── src/
│       │   ├── App.vue
│       │   ├── components/
│       │   │   ├── Login.vue
│       │   │   ├── MoyuFooter.vue
│       │   │   └── MoyuNav.vue
│       │   ├── layouts/
│       │   │   └── PageLayout.vue
│       │   ├── main.js
│       │   ├── pages/
│       │   │   ├── AddUrlPage.vue
│       │   │   ├── LoginPage.vue
│       │   │   └── MainPage.vue
│       │   └── router/
│       │       └── index.js
│       └── static/
│           └── .gitkeep
└── page/
    ├── backend/
    │   ├── .gitignore
    │   ├── controller/
    │   │   └── system.go
    │   ├── go.mod
    │   ├── go.sum
    │   ├── main.go
    │   ├── middleware/
    │   │   └── ws_options.go
    │   └── service/
    │       ├── cpu.go
    │       ├── disk.go
    │       ├── host.go
    │       ├── memory.go
    │       ├── network.go
    │       ├── page_data.go
    │       └── system.go
    └── frontend/
        ├── .babelrc
        ├── .editorconfig
        ├── .gitignore
        ├── .postcssrc.js
        ├── README.md
        ├── build/
        │   ├── build.js
        │   ├── check-versions.js
        │   ├── utils.js
        │   ├── vue-loader.conf.js
        │   ├── webpack.base.conf.js
        │   ├── webpack.dev.conf.js
        │   └── webpack.prod.conf.js
        ├── config/
        │   ├── dev.env.js
        │   ├── index.js
        │   └── prod.env.js
        ├── index.html
        ├── package.json
        ├── src/
        │   ├── App.vue
        │   ├── common/
        │   │   └── filters.js
        │   ├── components/
        │   │   ├── Cpu.vue
        │   │   ├── Disk.vue
        │   │   ├── Host.vue
        │   │   └── Mem.vue
        │   ├── layouts/
        │   │   └── Main.vue
        │   ├── main.js
        │   ├── pages/
        │   │   └── SystemPage.vue
        │   └── router/
        │       └── index.js
        └── static/
            └── .gitkeep

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/workflows/release.yml
================================================
name: 编译并发布项目
on:
  push:
    tags:
      - 'v*'
jobs:
  release:
    name: "编译发布release程序"
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [18.x]
        go-version: [1.19.x]
    steps:
      - uses: actions/checkout@v3
      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node-version }}
      - name: Use Go ${{ matrix.go-version }}
        uses: actions/setup-go@v3
        with:
          go-version: ${{ matrix.go-version }}
      - name: Build moyu-manager frontend
        working-directory: ./manager/frontend
        run: |
          npm install
          npm run build
      - name: Build moyu-manager release binary
        working-directory: ./manager/backend
        run: |
          go get .
          GOOS=linux GOARCH=amd64 go build --ldflags="-w -s" -o moyu-manager-linux-amd64 .
          GOOS=linux GOARCH=arm64 go build --ldflags="-w -s" -o moyu-manager-linux-arm64 .
          zip -r ../../moyu-manager-linux-amd64.zip moyu-manager-linux-amd64
          zip -r ../../moyu-manager-linux-arm64.zip moyu-manager-linux-arm64
          zip -r ../../init.sql.zip database/init.sql
      - name: Build moyu-page frontend
        working-directory: ./page/frontend
        run: |
          npm install
          npm run build
      - name: Build moyu-page release binary
        working-directory: ./page/backend
        run: |
          go get .
          GOOS=linux GOARCH=amd64 go build --ldflags="-w -s" -o moyu-page-linux-amd64 .
          GOOS=linux GOARCH=arm64 go build --ldflags="-w -s" -o moyu-page-linux-arm64 .
          zip -r ../../moyu-page-linux-amd64.zip moyu-page-linux-amd64
          zip -r ../../moyu-page-linux-arm64.zip moyu-page-linux-arm64
      - uses: "marvinpinto/action-automatic-releases@latest"
        with:
          repo_token: "${{ secrets.GITHUB_TOKEN }}"
          prerelease: false
          files: |
            *.zip
            

================================================
FILE: .gitignore
================================================
target/

================================================
FILE: Makefile
================================================
all: manager page
	mkdir -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

manager: manager-frontend
	cd manager/backend && go build -o moyu-manager --ldflags="-w -s" .

manager-frontend:
	cd manager/frontend && npm install && npm run build

page: page-frontend
	cd page/backend && go build -o moyu-page --ldflags="-w -s" .

page-frontend:
	cd page/frontend && npm install && npm run build

clean:
	-rm -rf page/backend/dist page/backend/moyu-page manager/backend/dist manager/backend/moyu-manager target

.PHONY: page-frontend page manager-frontend manager clean


================================================
FILE: ManagerDockerfile
================================================
FROM alpine:3.17.1 as builder
COPY . .
RUN apk add go nodejs npm
RUN cd manager/frontend && npm install && npm run build && \
    cd ../backend && go get . && go build -o /tmp/moyu-manager --ldflags="-w -s" .

FROM alpine:3.17.1 as deploy
EXPOSE 8080
RUN mkdir -p /moyu-manager/bin && mkdir -p /moyu-manager/db
COPY --from=builder /tmp/moyu-manager /moyu-manager/bin/
ENTRYPOINT /moyu-manager/bin/moyu-manager

================================================
FILE: PageDockerfile
================================================
FROM alpine:3.17.1 as builder
COPY . .
RUN apk add go nodejs npm
RUN cd page/frontend && npm install && npm run build && \
    cd ../backend && go get . && go build -o moyu-page --ldflags="-w -s" .

FROM alpine:3.17.1 as deploy
EXPOSE 8081
COPY --from=builder backend/moyu-page .
ENTRYPOINT /moyu-page

================================================
FILE: README.md
================================================
# 墨鱼探针

## 简介

墨鱼探针为主从模式,主为`墨鱼manager`,从为`墨鱼page`。`墨鱼manager`可获取展示多个墨鱼page的简要状态信息,登录后可点击title进入墨鱼page页面。`墨鱼page`独立部署,可独立使用。

技术栈:go(fiber) + vue2 + element-ui + nes.css。

最终将前端和后端全部编译到单个二进制程序中,分为`moyu-manager`和`moyu-page`2个二进制程序。

## 展示

### 墨鱼page

![moyu_page](./imgs/moyu_page.png)

### 墨鱼manager

未登录主页:

![moyu_manager_main_unlogin](./imgs/moyu_manager_main_unlogin.png)

已登录主页:

![moyu_manager_main_login](./imgs/moyu_manager_main_login.png)

登录页:

![moyu_manager_login](./imgs/moyu_manager_login.png)

添加墨鱼page页面:

![moyu_manager_add_page](./imgs/moyu_manager_add_page.png)

## 直接使用release文件

### moyu-page

1. 直接运行
    ```bash
    PORT=8081 ./moyu-page
    ```

### moyu-manager

1. 初始化数据库(第一次运行)
    ```bash
    cat init.sql | sqlite3 db/moyu_manager.db
    ```
2. 目录结构
    ```bash
    ├── bin
    │   └── moyu-manager
    └── db
        └── moyu_manager.db
    ```
3. 运行
    ```bash
    PORT=8080 ./bin/moyu-manager
    ```

4. 添加墨鱼page。如本项目在线demo:`https://moyu-manager.linux.plus/`,直接复制墨鱼page主页`https://moyu-page.linux.plus/`到`添加墨鱼page页面`即可。在墨鱼manager中,如果已登录,可以点击title访问墨鱼page。

## 编译使用

### 依赖:

make: ^4.0

nodejs: ^18.0

go: ^1.19.0

sqlite: ^3.0

### 一键编译

1. 进入项目目录。
    ```bash
    cd moyu
    ```
2. 一键编译。
    ```bash
    make
    ```
3. 运行墨鱼manager和墨鱼page。
    ```bash
    PORT=8081 target/bin/moyu-page
    PORT=8080 target/bin/moyu-manager
    ```
4. (可选)清理项目,删除编译的墨鱼探针二进制等文件。
    ```bash
    make clean
    ```

### 手动编译

#### 编译墨鱼page

1. 进入项目目录。
    ```bash
    cd moyu/page
    ```
2. 编译前端资源。
    ```bash
    cd frontend
    npm run build
    ```
3. 编译后端项目。
    ```bash
    cd ../backend
    go build -o moyu-page --ldflags="-w -s" .
    ```

#### 编译墨鱼manager

1. 进入项目目录。
    ```bash
    cd moyu/manager
    ```
2. 编译前端资源。
    ```bash
    cd frontend
    npm run build
    ```
3. 编译后端项目。
    ```bash
    cd ../backend
    go build -o moyu-manager --ldflags="-w -s" .
    ```

## Docker运行

### 前提说明

#### 墨鱼page

墨鱼page直接运行docker无法获取宿主机信息,需要添加一些运行参数:

1. 由于需要获取宿主机网络接口流量,所以需要以host方式运行docker。
2. 获取磁盘分区信息需要`/proc/N/mountinfo`,所以需要将宿主机的某个进程的文件挂载到docker中,然后设置`HOST_PROC_MOUNTINFO`并运行项目。
3. 项目`PORT`变量默认`8081`,可自行指定其他端口。

#### 墨鱼manager

1. 墨鱼manager依赖`sqlite3`,需要使用`moyu/manager/backend/database/init.sql`创建和初始化用户表数据。
2. 请使用一下目录格式存放`moyu-manager`程序和数据库,容器挂载时需要注意目录位置,默认编译的项目根目录为`/moyu-manager`。
    ```bash
    root@liuxu:/moyu-manager# tree
    .
    ├── bin
    │   └── moyu-manager
    └── db
        └── moyu_manager.db
    ```

### 运行容器

#### 墨鱼page

1. 编译page镜像。
    ```bash
    docker build -t moyu-page -f PageDockerfile .
    ```
2. 单磁盘挂载情况下运行,其中`--network=host`指定使用宿主机网络,`--mount`挂载`dockerd`的进程`mountinfo`文件到docker中,并设置`HOST_PROC_MOUNTINFO`为挂载的文件路径。
    ```bash
    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
    ```
3. (可选)如果还有其他分区,如我的`/boot/efi`挂载到了独立分区,想获取到这个分区信息,需要把这个目录挂载到docker中。
    ```bash
    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
    ```

#### 墨鱼manager

1. 编译manager镜像。
    ```bash
    docker build -t moyu-manager -f ManagerDockerfile .
    ```
2. 创建数据库。
    ```bash
    mkdir db
    cat manager/backend/database/init.sql | sqlite3 db/moyu_manager.db
    ```
3. 挂载数据库运行。
    ```bash
    docker run -e PORT=8080 -p 8080:8080 -v ./db:/moyu-manager/db  moyu-manager
    ```

## FAQ

1. 我使用nginx做反代,设置了`location /moyu{}`该怎么办

    答:运行墨鱼page或墨鱼manager时,添加`BASEURL=/moyu`环境变量运行。

2. 运行墨鱼manager,程序报找不到`moyu_manager.db`怎么办

    答:确认自己的目录结构是不是如下:
    ```bash
    ├── bin
    │   └── moyu-manager
    └── db
        └── moyu_manager.db
    ```
    然后进入`bin/`目录下运行`moyu-manager`

3. 我想修改账号或者token

    答:运行sqlite3,通过sql修改。
    ```bash
    sqlite3 moyu_manager.db
    sqlite> UPDATE users SET token='token123' WHERE user='user'
    ```

4. 我想创建新的账户和token

    答:运行sqlite3,通过sql添加。
    ```bash
    sqlite3 moyu_manager.db
    sqlite> INSERT INTO users(user, token) VALUES('user1', 'token1');
    ```


================================================
FILE: manager/backend/.gitignore
================================================
dist/
moyu-manager

================================================
FILE: manager/backend/controller/page_data_controller/page_data_controller.go
================================================
package page_data_controller

import (
	"encoding/json"
	"net/http"
	"time"

	"github.com/gofiber/websocket/v2"
	"github.com/liuquanhao/moyu/model"
	"github.com/liuquanhao/moyu/model/remote_url_model"
)

// define: page/backend/service/page_data.go
type PageData struct {
	Uptime        uint64  `json:"uptime"`
	CPUPercent    float64 `json:"cpu_percent"`
	MemoryPercent float64 `json:"memory_percent"`
	DiskPercent   float64 `json:"disk_percent"`
	NetSendTotal  uint64  `json:"net_send"`
	NetRecvTotal  uint64  `json:"net_recv"`
	Timestamp     int64   `json:"timestamp"`
}

type PageInfo struct {
	PageId   uint32    `json:"page_id"`
	PageData *PageData `json:"page_data"`
}

func getPageData(myClient *http.Client, url string, target interface{}) error {
	resp, err := http.Get(url + "api/page_data")
	if err != nil {
		return err
	}
	if resp.StatusCode != 200 {
		return err
	}
	defer resp.Body.Close()
	return json.NewDecoder(resp.Body).Decode(target)
}

func loopPullPageData(pageId uint32, url string, ch chan *PageInfo) {
	var myClient = &http.Client{Timeout: 5 * time.Second}
	pageData := new(PageData)
	for {
		start := time.Now()
		err := getPageData(myClient, url, pageData)
		if err != nil {
			time.Sleep(3 * time.Second)
		} else {
			ch <- &PageInfo{
				PageId:   pageId,
				PageData: pageData,
			}
		}
		end := time.Now()
		useTime := end.Sub(start)
		if useTime < 1*time.Second {
			time.Sleep(1000*time.Millisecond - useTime)
		}
	}
}

func PushPageData(c *websocket.Conn) {
	db := model.DBConn
	var pageUrls []remote_url_model.PageUrl
	db.Find(&pageUrls)
	ch := make(chan *PageInfo, len(pageUrls))
	defer close(ch)
	for _, pageUrl := range pageUrls {
		go loopPullPageData(pageUrl.Id, pageUrl.PageUrl, ch)
	}
	for pageInfo := range ch {
		c.WriteJSON(pageInfo)
	}
}


================================================
FILE: manager/backend/controller/page_url_controller/page_url_controller.go
================================================
package page_url_controller

import (
	"github.com/gofiber/fiber/v2"
	"github.com/liuquanhao/moyu/model"
	"github.com/liuquanhao/moyu/model/remote_url_model"

	"github.com/go-playground/validator/v10"
)

var validate = validator.New()

func Add(c *fiber.Ctx) error {
	db := model.DBConn
	pageUrl := new(remote_url_model.PageUrl)
	if err := c.BodyParser(pageUrl); err != nil {
		return c.Status(fiber.StatusBadRequest).SendString(err.Error())
	}
	if err := validate.Struct(pageUrl); err != nil {
		return c.Status(fiber.StatusBadRequest).SendString(err.Error())
	}
	db.Create(&pageUrl)
	return c.JSON(pageUrl)
}

func Delete(c *fiber.Ctx) error {
	id := c.Params("id")
	db := model.DBConn
	var pageUrl remote_url_model.PageUrl
	db.Take(&pageUrl, id)
	if pageUrl.Title == "" {
		return c.Status(fiber.StatusNotFound).SendString("Not Found")
	}
	db.Delete(&pageUrl)
	return c.SendString("Delete success")
}

func List(c *fiber.Ctx) error {
	db := model.DBConn
	var pageUrls []remote_url_model.PageUrl
	db.Find(&pageUrls)
	s, _ := model.SessionStore.Get(c)
	if !s.Fresh() {
		return c.JSON(pageUrls)
	}
	// unlogin
	for _, pageUrl := range pageUrls {
		pageUrl.PageUrl = ""
	}
	return c.JSON(pageUrls)
}


================================================
FILE: manager/backend/controller/user_controller/user_controller.go
================================================
package user_controller

import (
	"strconv"
	"time"

	"github.com/go-playground/validator/v10"
	"github.com/gofiber/fiber/v2"
	"github.com/liuquanhao/moyu/model"
	"github.com/liuquanhao/moyu/model/user_model"
)

var validate = validator.New()

func Login(c *fiber.Ctx) error {
	db := model.DBConn
	user := new(user_model.User)
	if err := c.BodyParser(user); err != nil {
		return c.Status(fiber.StatusBadRequest).SendString(err.Error())
	}
	if err := validate.Struct(user); err != nil {
		return c.Status(fiber.StatusBadRequest).SendString(err.Error())
	}
	db.Where(&user).Take(&user)
	if user.Id == 0 {
		return c.Status(fiber.StatusUnauthorized).SendString("Not Found User")
	}
	s, _ := model.SessionStore.Get(c)
	s.Set("uid", user.Id)
	s.Set("sid", s.ID())
	s.Set("ip", c.Context().RemoteIP().String())
	s.Set("login", time.Unix(time.Now().Unix(), 0).UTC().String())
	s.Set("ua", string(c.Request().Header.UserAgent()))
	err := s.Save()
	if err != nil {
		return c.Status(fiber.StatusServiceUnavailable).SendString(err.Error())
	}
	cookie := new(fiber.Cookie)
	cookie.Name = "uid"
	cookie.Value = strconv.FormatUint(uint64(user.Id), 10)
	cookie.Expires = time.Now().Add(24 * time.Hour)
	c.Cookie(cookie)
	c.Status(fiber.StatusNoContent)
	return nil
}

func Account(c *fiber.Ctx) error {
	s, _ := model.SessionStore.Get(c)
	if s.Fresh() {
		c.Status(fiber.StatusNotFound)
		return nil
	}
	return c.JSON(fiber.Map{
		"sid":   s.ID(),
		"uid":   s.Get("uid"),
		"ip":    s.Get("ip"),
		"login": s.Get("login"),
		"ua":    s.Get("ua"),
	})
}

func Logout(c *fiber.Ctx) error {
	s, _ := model.SessionStore.Get(c)
	if err := s.Destroy(); err != nil {
		return c.Status(fiber.StatusServiceUnavailable).SendString(err.Error())
	}
	c.Status(fiber.StatusNoContent)
	return nil
}


================================================
FILE: manager/backend/database/init.sql
================================================
CREATE TABLE IF NOT EXISTS sessions (
    k  VARCHAR(64) PRIMARY KEY NOT NULL DEFAULT '',
    v  BLOB NOT NULL,
    e  BIGINT NOT NULL DEFAULT '0',
    u  TEXT
);
CREATE TABLE IF NOT EXISTS users (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    user VARCHAR(16) UNIQUE NOT NULL,
    token VARCHAR(32) NOT NULL
);
CREATE TABLE IF NOT EXISTS page_urls (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    title VARCHAR(32) NOT NULL,
    page_url VARCHAR(512) NOT NULL
);

CREATE UNIQUE INDEX user_uiq on users (user);
INSERT INTO users(user, token) VALUES ("user", "token123");

================================================
FILE: manager/backend/go.mod
================================================
module github.com/liuquanhao/moyu

go 1.19

require (
	github.com/go-playground/validator/v10 v10.11.2
	github.com/gofiber/fiber/v2 v2.42.0
	github.com/gofiber/storage/sqlite3 v0.0.0-20230206084615-41a84b36b572
	github.com/gofiber/websocket/v2 v2.1.4
	gorm.io/driver/sqlite v1.4.4
	gorm.io/gorm v1.24.5
)

require (
	github.com/andybalholm/brotli v1.0.4 // indirect
	github.com/fasthttp/websocket v1.5.1 // indirect
	github.com/go-playground/locales v0.14.1 // indirect
	github.com/go-playground/universal-translator v0.18.1 // indirect
	github.com/google/uuid v1.3.0 // indirect
	github.com/jinzhu/inflection v1.0.0 // indirect
	github.com/jinzhu/now v1.1.5 // indirect
	github.com/klauspost/compress v1.15.9 // indirect
	github.com/leodido/go-urn v1.2.1 // indirect
	github.com/mattn/go-colorable v0.1.13 // indirect
	github.com/mattn/go-isatty v0.0.17 // indirect
	github.com/mattn/go-runewidth v0.0.14 // indirect
	github.com/mattn/go-sqlite3 v1.14.16 // indirect
	github.com/philhofer/fwd v1.1.1 // indirect
	github.com/rivo/uniseg v0.2.0 // indirect
	github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94 // indirect
	github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d // indirect
	github.com/tinylib/msgp v1.1.6 // indirect
	github.com/valyala/bytebufferpool v1.0.0 // indirect
	github.com/valyala/fasthttp v1.44.0 // indirect
	github.com/valyala/tcplisten v1.0.0 // indirect
	golang.org/x/crypto v0.5.0 // indirect
	golang.org/x/sys v0.4.0 // indirect
	golang.org/x/text v0.6.0 // indirect
)


================================================
FILE: manager/backend/go.sum
================================================
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/fasthttp/websocket v1.5.1 h1:iZsMv5OtZ1E52hhCnlOm/feLCrPhutlrZgvEGcZa1FM=
github.com/fasthttp/websocket v1.5.1/go.mod h1:s+gJkEn38QXLkNfOe/n75Yb8we+VEho1vYqeUYheomw=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU=
github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s=
github.com/gofiber/fiber/v2 v2.42.0 h1:Fnp7ybWvS+sjNQsFvkhf4G8OhXswvB6Vee8hM/LyS+8=
github.com/gofiber/fiber/v2 v2.42.0/go.mod h1:3+SGNjqMh5VQH5Vz2Wdi43zTIV16ktlFd3x3R6O1Zlc=
github.com/gofiber/storage/sqlite3 v0.0.0-20230206084615-41a84b36b572 h1:FHnNfM4q4Z+WtuYoQ5vz2z+JWmsY/sV49IS1nekEko4=
github.com/gofiber/storage/sqlite3 v0.0.0-20230206084615-41a84b36b572/go.mod h1:TXYMAnzU/KAYE7RrM5tH9FrFC9Sr0CgMySWpMedmJR8=
github.com/gofiber/utils v1.0.1 h1:knct4cXwBipWQqFrOy1Pv6UcgPM+EXo9jDgc66V1Qio=
github.com/gofiber/utils v1.0.1/go.mod h1:pacRFtghAE3UoknMOUiXh2Io/nLWSUHtQCi/3QASsOc=
github.com/gofiber/websocket/v2 v2.1.4 h1:Ki6L7auleAwgi7iRmtUiWKltlbmtkCJ0COtK1nt8L3g=
github.com/gofiber/websocket/v2 v2.1.4/go.mod h1:IC4ZUejlk0kJSaphJ1gjqgKfK9fhw8eoAr3/UdbOzEA=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY=
github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/philhofer/fwd v1.1.1 h1:GdGcTjf5RNAxwS4QLsiMzJYj5KEvPJD3Abr261yRQXQ=
github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94 h1:rmMl4fXJhKMNWl+K+r/fq4FbbKI+Ia2m9hYBLm2h4G4=
github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94/go.mod h1:90zrgN3D/WJsDd1iXHT96alCoN2KJo6/4x1DZC3wZs8=
github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d h1:Q+gqLBOPkFGHyCJxXMRqtUgUbTjI8/Ze8vu8GGyNFwo=
github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d/go.mod h1:Gy+0tqhJvgGlqnTF8CVGP0AaGRjwBtXs/a5PA0Y3+A4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/tinylib/msgp v1.1.6 h1:i+SbKraHhnrf9M5MYmvQhFnbLhAXSDWF8WWsuyRdocw=
github.com/tinylib/msgp v1.1.6/go.mod h1:75BAfg2hauQhs3qedfdDZmWAPcFMAvJE5b9rGOMufyw=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.44.0 h1:R+gLUhldIsfg1HokMuQjdQ5bh9nuXHPIfvkYUu9eR5Q=
github.com/valyala/fasthttp v1.44.0/go.mod h1:f6VbjjoI3z1NDOZOv17o6RvtRSWxC77seBFc2uWtgiY=
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220906165146-f3363e06e74c/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gorm.io/driver/sqlite v1.4.4 h1:gIufGoR0dQzjkyqDyYSCvsYR6fba1Gw5YKDqKeChxFc=
gorm.io/driver/sqlite v1.4.4/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI=
gorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
gorm.io/gorm v1.24.5 h1:g6OPREKqqlWq4kh/3MCQbZKImeB9e6Xgc4zD+JgNZGE=
gorm.io/gorm v1.24.5/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=


================================================
FILE: manager/backend/main.go
================================================
package main

import (
	"embed"
	"io/fs"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"time"

	"github.com/liuquanhao/moyu/controller/page_data_controller"
	"github.com/liuquanhao/moyu/controller/page_url_controller"
	"github.com/liuquanhao/moyu/controller/user_controller"
	"github.com/liuquanhao/moyu/middleware"
	"github.com/liuquanhao/moyu/model"

	"github.com/gofiber/fiber/v2"
	"github.com/gofiber/fiber/v2/middleware/cors"
	"github.com/gofiber/fiber/v2/middleware/filesystem"
	"github.com/gofiber/fiber/v2/middleware/session"
	"github.com/gofiber/storage/sqlite3"
	"github.com/gofiber/websocket/v2"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
)

func getCurrentPwd() string {
	exePath, _ := os.Executable()
	return filepath.Dir(exePath)
}
func initDatabase() {
	var err error
	model.DBConn, err = gorm.Open(sqlite.Open(getCurrentPwd() + "/../db/moyu_manager.db"))
	if err != nil {
		log.Fatal("failed to connect database: {}", err)
	}
}

func initSession() {
	storage := sqlite3.New(sqlite3.Config{
		Database:        getCurrentPwd() + "/../db/moyu_manager.db",
		Table:           "sessions",
		Reset:           false,
		GCInterval:      10 * time.Second,
		MaxOpenConns:    100,
		MaxIdleConns:    100,
		ConnMaxLifetime: 1 * time.Second,
	})

	model.SessionStore = session.New(session.Config{
		Storage: storage,
	})
}

func setupRoutes(app *fiber.App) {
	app.Use(cors.New(cors.Config{
		AllowOrigins:     "*",
		AllowCredentials: true,
	}))
	base := app.Group(os.Getenv("BASEURL"))

	ws := base.Group("/ws")
	ws.Use("/*", middleware.UpgradeOptions)
	ws.Get("/page_data", websocket.New(page_data_controller.PushPageData))

	api := base.Group("/api")
	api.Post("/login", user_controller.Login)
	api.Get("/page_url", page_url_controller.List)

	logged := app.Group("/api", middleware.Logged)
	logged.Post("/logout", user_controller.Logout)
	logged.Get("/account", user_controller.Account)
	logged.Post("/page_url", page_url_controller.Add)
	logged.Delete("/page_url/:id", page_url_controller.Delete)

	stripped, err := fs.Sub(frontend, "dist")
	if err != nil {
		log.Fatal(err)
	}
	base.Use("/", filesystem.New(filesystem.Config{
		Root:   http.FS(stripped),
		Browse: true,
	}))
}

//go:embed dist
var frontend embed.FS

func main() {
	app := fiber.New()

	initDatabase()
	initSession()
	setupRoutes(app)

	host := os.Getenv("HOST")
	port := os.Getenv("PORT")
	log.Println(host + ":" + port)
	log.Fatal(app.Listen(host + ":" + port))
}


================================================
FILE: manager/backend/middleware/logged.go
================================================
package middleware

import (
	"github.com/gofiber/fiber/v2"
	"github.com/liuquanhao/moyu/model"
)

func Logged(c *fiber.Ctx) error {

	s, _ := model.SessionStore.Get(c)
	if s.Fresh() {
		c.Status(fiber.StatusUnauthorized)
		return nil
	}
	return c.Next()
}


================================================
FILE: manager/backend/middleware/ws_options.go
================================================
package middleware

import (
	"github.com/gofiber/fiber/v2"
	"github.com/gofiber/websocket/v2"
)

func UpgradeOptions(c *fiber.Ctx) error {
	if websocket.IsWebSocketUpgrade(c) {
		return c.Next()
	}
	return fiber.ErrUpgradeRequired
}


================================================
FILE: manager/backend/model/db.go
================================================
package model

import "gorm.io/gorm"

var (
	DBConn *gorm.DB
)


================================================
FILE: manager/backend/model/remote_url_model/remote_url_model.go
================================================
package remote_url_model

type PageUrl struct {
	Id      uint32 `json:"id"`
	Title   string `json:"title" validate:"required,max=32"`
	PageUrl string `json:"page_url" validate:"required,max=512"`
}


================================================
FILE: manager/backend/model/session.go
================================================
package model

import "github.com/gofiber/fiber/v2/middleware/session"

var (
	SessionStore *session.Store
)


================================================
FILE: manager/backend/model/user_model/user_model.go
================================================
package user_model

type User struct {
	Id    uint32 `json:"id"`
	User  string `json:"user" validate:"required,max=16"`
	Token string `json:"token" validate:"required,max=32"`
}


================================================
FILE: manager/frontend/.babelrc
================================================
{
  "presets": [
    ["env", {
      "modules": false,
      "targets": {
        "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
      }
    }],
    "stage-2"
  ],
  "plugins": ["transform-vue-jsx", "transform-runtime"]
}


================================================
FILE: manager/frontend/.editorconfig
================================================
root = true

[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true


================================================
FILE: manager/frontend/.gitignore
================================================
.DS_Store
node_modules/
/dist/
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln


================================================
FILE: manager/frontend/.postcssrc.js
================================================
// https://github.com/michael-ciniawsky/postcss-load-config

module.exports = {
  "plugins": {
    "postcss-import": {},
    "postcss-url": {},
    // to edit target browsers: use "browserslist" field in package.json
    "autoprefixer": {}
  }
}


================================================
FILE: manager/frontend/README.md
================================================
# frontend

> moyu manager

## Build Setup

``` bash
# install dependencies
npm install

# serve with hot reload at localhost:8080
npm run dev

# build for production with minification
npm run build

# build for production and view the bundle analyzer report
npm run build --report
```

For 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).


================================================
FILE: manager/frontend/build/build.js
================================================
'use strict'
require('./check-versions')()

process.env.NODE_ENV = 'production'

const ora = require('ora')
const rm = require('rimraf')
const path = require('path')
const chalk = require('chalk')
const webpack = require('webpack')
const config = require('../config')
const webpackConfig = require('./webpack.prod.conf')

const spinner = ora('building for production...')
spinner.start()

rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
  if (err) throw err
  webpack(webpackConfig, (err, stats) => {
    spinner.stop()
    if (err) throw err
    process.stdout.write(stats.toString({
      colors: true,
      modules: false,
      children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build.
      chunks: false,
      chunkModules: false
    }) + '\n\n')

    if (stats.hasErrors()) {
      console.log(chalk.red('  Build failed with errors.\n'))
      process.exit(1)
    }

    console.log(chalk.cyan('  Build complete.\n'))
    console.log(chalk.yellow(
      '  Tip: built files are meant to be served over an HTTP server.\n' +
      '  Opening index.html over file:// won\'t work.\n'
    ))
  })
})


================================================
FILE: manager/frontend/build/check-versions.js
================================================
'use strict'
const chalk = require('chalk')
const semver = require('semver')
const packageConfig = require('../package.json')
const shell = require('shelljs')

function exec (cmd) {
  return require('child_process').execSync(cmd).toString().trim()
}

const versionRequirements = [
  {
    name: 'node',
    currentVersion: semver.clean(process.version),
    versionRequirement: packageConfig.engines.node
  }
]

if (shell.which('npm')) {
  versionRequirements.push({
    name: 'npm',
    currentVersion: exec('npm --version'),
    versionRequirement: packageConfig.engines.npm
  })
}

module.exports = function () {
  const warnings = []

  for (let i = 0; i < versionRequirements.length; i++) {
    const mod = versionRequirements[i]

    if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
      warnings.push(mod.name + ': ' +
        chalk.red(mod.currentVersion) + ' should be ' +
        chalk.green(mod.versionRequirement)
      )
    }
  }

  if (warnings.length) {
    console.log('')
    console.log(chalk.yellow('To use this template, you must update following to modules:'))
    console.log()

    for (let i = 0; i < warnings.length; i++) {
      const warning = warnings[i]
      console.log('  ' + warning)
    }

    console.log()
    process.exit(1)
  }
}


================================================
FILE: manager/frontend/build/utils.js
================================================
'use strict'
const path = require('path')
const config = require('../config')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const packageConfig = require('../package.json')

exports.assetsPath = function (_path) {
  const assetsSubDirectory = process.env.NODE_ENV === 'production'
    ? config.build.assetsSubDirectory
    : config.dev.assetsSubDirectory

  return path.posix.join(assetsSubDirectory, _path)
}

exports.cssLoaders = function (options) {
  options = options || {}

  const cssLoader = {
    loader: 'css-loader',
    options: {
      sourceMap: options.sourceMap
    }
  }

  const postcssLoader = {
    loader: 'postcss-loader',
    options: {
      sourceMap: options.sourceMap
    }
  }

  // generate loader string to be used with extract text plugin
  function generateLoaders (loader, loaderOptions) {
    const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader]

    if (loader) {
      loaders.push({
        loader: loader + '-loader',
        options: Object.assign({}, loaderOptions, {
          sourceMap: options.sourceMap
        })
      })
    }

    // Extract CSS when that option is specified
    // (which is the case during production build)
    if (options.extract) {
      return ExtractTextPlugin.extract({
        use: loaders,
        fallback: 'vue-style-loader'
      })
    } else {
      return ['vue-style-loader'].concat(loaders)
    }
  }

  // https://vue-loader.vuejs.org/en/configurations/extract-css.html
  return {
    css: generateLoaders(),
    postcss: generateLoaders(),
    less: generateLoaders('less'),
    sass: generateLoaders('sass', { indentedSyntax: true }),
    scss: generateLoaders('sass'),
    stylus: generateLoaders('stylus'),
    styl: generateLoaders('stylus')
  }
}

// Generate loaders for standalone style files (outside of .vue)
exports.styleLoaders = function (options) {
  const output = []
  const loaders = exports.cssLoaders(options)

  for (const extension in loaders) {
    const loader = loaders[extension]
    output.push({
      test: new RegExp('\\.' + extension + '$'),
      use: loader
    })
  }

  return output
}

exports.createNotifierCallback = () => {
  const notifier = require('node-notifier')

  return (severity, errors) => {
    if (severity !== 'error') return

    const error = errors[0]
    const filename = error.file && error.file.split('!').pop()

    notifier.notify({
      title: packageConfig.name,
      message: severity + ': ' + error.name,
      subtitle: filename || '',
      icon: path.join(__dirname, 'logo.png')
    })
  }
}


================================================
FILE: manager/frontend/build/vue-loader.conf.js
================================================
'use strict'
const utils = require('./utils')
const config = require('../config')
const isProduction = process.env.NODE_ENV === 'production'
const sourceMapEnabled = isProduction
  ? config.build.productionSourceMap
  : config.dev.cssSourceMap

module.exports = {
  loaders: utils.cssLoaders({
    sourceMap: sourceMapEnabled,
    extract: isProduction
  }),
  cssSourceMap: sourceMapEnabled,
  cacheBusting: config.dev.cacheBusting,
  transformToRequire: {
    video: ['src', 'poster'],
    source: 'src',
    img: 'src',
    image: 'xlink:href'
  }
}


================================================
FILE: manager/frontend/build/webpack.base.conf.js
================================================
'use strict'
const path = require('path')
const utils = require('./utils')
const config = require('../config')
const vueLoaderConfig = require('./vue-loader.conf')

function resolve (dir) {
  return path.join(__dirname, '..', dir)
}



module.exports = {
  context: path.resolve(__dirname, '../'),
  entry: {
    app: './src/main.js'
  },
  output: {
    path: config.build.assetsRoot,
    filename: '[name].js',
    publicPath: process.env.NODE_ENV === 'production'
      ? config.build.assetsPublicPath
      : config.dev.assetsPublicPath
  },
  resolve: {
    extensions: ['.js', '.vue', '.json'],
    alias: {
      'vue$': 'vue/dist/vue.esm.js',
      '@': resolve('src'),
    }
  },
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: vueLoaderConfig
      },
      {
        test: /\.js$/,
        loader: 'babel-loader',
        include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]
      },
      {
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: utils.assetsPath('img/[name].[hash:7].[ext]')
        }
      },
      {
        test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: utils.assetsPath('media/[name].[hash:7].[ext]')
        }
      },
      {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
        }
      }
    ]
  },
  node: {
    // prevent webpack from injecting useless setImmediate polyfill because Vue
    // source contains it (although only uses it if it's native).
    setImmediate: false,
    // prevent webpack from injecting mocks to Node native modules
    // that does not make sense for the client
    dgram: 'empty',
    fs: 'empty',
    net: 'empty',
    tls: 'empty',
    child_process: 'empty'
  }
}


================================================
FILE: manager/frontend/build/webpack.dev.conf.js
================================================
'use strict'
const utils = require('./utils')
const webpack = require('webpack')
const config = require('../config')
const merge = require('webpack-merge')
const path = require('path')
const baseWebpackConfig = require('./webpack.base.conf')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
const portfinder = require('portfinder')

const HOST = process.env.HOST
const PORT = process.env.PORT && Number(process.env.PORT)

const devWebpackConfig = merge(baseWebpackConfig, {
  module: {
    rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true })
  },
  // cheap-module-eval-source-map is faster for development
  devtool: config.dev.devtool,

  // these devServer options should be customized in /config/index.js
  devServer: {
    clientLogLevel: 'warning',
    historyApiFallback: {
      rewrites: [
        { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') },
      ],
    },
    hot: true,
    contentBase: false, // since we use CopyWebpackPlugin.
    compress: true,
    host: HOST || config.dev.host,
    port: PORT || config.dev.port,
    open: config.dev.autoOpenBrowser,
    overlay: config.dev.errorOverlay
      ? { warnings: false, errors: true }
      : false,
    publicPath: config.dev.assetsPublicPath,
    proxy: config.dev.proxyTable,
    quiet: true, // necessary for FriendlyErrorsPlugin
    watchOptions: {
      poll: config.dev.poll,
    }
  },
  plugins: [
    new webpack.DefinePlugin({
      'process.env': require('../config/dev.env')
    }),
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update.
    new webpack.NoEmitOnErrorsPlugin(),
    // https://github.com/ampedandwired/html-webpack-plugin
    new HtmlWebpackPlugin({
      filename: 'index.html',
      template: 'index.html',
      inject: true
    }),
    // copy custom static assets
    new CopyWebpackPlugin([
      {
        from: path.resolve(__dirname, '../static'),
        to: config.dev.assetsSubDirectory,
        ignore: ['.*']
      }
    ])
  ]
})

module.exports = new Promise((resolve, reject) => {
  portfinder.basePort = process.env.PORT || config.dev.port
  portfinder.getPort((err, port) => {
    if (err) {
      reject(err)
    } else {
      // publish the new Port, necessary for e2e tests
      process.env.PORT = port
      // add port to devServer config
      devWebpackConfig.devServer.port = port

      // Add FriendlyErrorsPlugin
      devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({
        compilationSuccessInfo: {
          messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`],
        },
        onErrors: config.dev.notifyOnErrors
        ? utils.createNotifierCallback()
        : undefined
      }))

      resolve(devWebpackConfig)
    }
  })
})


================================================
FILE: manager/frontend/build/webpack.prod.conf.js
================================================
'use strict'
const path = require('path')
const utils = require('./utils')
const webpack = require('webpack')
const config = require('../config')
const merge = require('webpack-merge')
const baseWebpackConfig = require('./webpack.base.conf')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')

const env = require('../config/prod.env')

const webpackConfig = merge(baseWebpackConfig, {
  module: {
    rules: utils.styleLoaders({
      sourceMap: config.build.productionSourceMap,
      extract: true,
      usePostCSS: true
    })
  },
  devtool: config.build.productionSourceMap ? config.build.devtool : false,
  output: {
    path: config.build.assetsRoot,
    filename: utils.assetsPath('js/[name].[chunkhash].js'),
    chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
  },
  plugins: [
    // http://vuejs.github.io/vue-loader/en/workflow/production.html
    new webpack.DefinePlugin({
      'process.env': env
    }),
    new UglifyJsPlugin({
      uglifyOptions: {
        compress: {
          warnings: false
        }
      },
      sourceMap: config.build.productionSourceMap,
      parallel: true
    }),
    // extract css into its own file
    new ExtractTextPlugin({
      filename: utils.assetsPath('css/[name].[contenthash].css'),
      // Setting the following option to `false` will not extract CSS from codesplit chunks.
      // Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack.
      // It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`, 
      // increasing file size: https://github.com/vuejs-templates/webpack/issues/1110
      allChunks: true,
    }),
    // Compress extracted CSS. We are using this plugin so that possible
    // duplicated CSS from different components can be deduped.
    new OptimizeCSSPlugin({
      cssProcessorOptions: config.build.productionSourceMap
        ? { safe: true, map: { inline: false } }
        : { safe: true }
    }),
    // generate dist index.html with correct asset hash for caching.
    // you can customize output by editing /index.html
    // see https://github.com/ampedandwired/html-webpack-plugin
    new HtmlWebpackPlugin({
      filename: config.build.index,
      template: 'index.html',
      inject: true,
      minify: {
        removeComments: true,
        collapseWhitespace: true,
        removeAttributeQuotes: true
        // more options:
        // https://github.com/kangax/html-minifier#options-quick-reference
      },
      // necessary to consistently work with multiple chunks via CommonsChunkPlugin
      chunksSortMode: 'dependency'
    }),
    // keep module.id stable when vendor modules does not change
    new webpack.HashedModuleIdsPlugin(),
    // enable scope hoisting
    new webpack.optimize.ModuleConcatenationPlugin(),
    // split vendor js into its own file
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
      minChunks (module) {
        // any required modules inside node_modules are extracted to vendor
        return (
          module.resource &&
          /\.js$/.test(module.resource) &&
          module.resource.indexOf(
            path.join(__dirname, '../node_modules')
          ) === 0
        )
      }
    }),
    // extract webpack runtime and module manifest to its own file in order to
    // prevent vendor hash from being updated whenever app bundle is updated
    new webpack.optimize.CommonsChunkPlugin({
      name: 'manifest',
      minChunks: Infinity
    }),
    // This instance extracts shared chunks from code splitted chunks and bundles them
    // in a separate chunk, similar to the vendor chunk
    // see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk
    new webpack.optimize.CommonsChunkPlugin({
      name: 'app',
      async: 'vendor-async',
      children: true,
      minChunks: 3
    }),

    // copy custom static assets
    new CopyWebpackPlugin([
      {
        from: path.resolve(__dirname, '../static'),
        to: config.build.assetsSubDirectory,
        ignore: ['.*']
      }
    ])
  ]
})

if (config.build.productionGzip) {
  const CompressionWebpackPlugin = require('compression-webpack-plugin')

  webpackConfig.plugins.push(
    new CompressionWebpackPlugin({
      asset: '[path].gz[query]',
      algorithm: 'gzip',
      test: new RegExp(
        '\\.(' +
        config.build.productionGzipExtensions.join('|') +
        ')$'
      ),
      threshold: 10240,
      minRatio: 0.8
    })
  )
}

if (config.build.bundleAnalyzerReport) {
  const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
  webpackConfig.plugins.push(new BundleAnalyzerPlugin())
}

module.exports = webpackConfig


================================================
FILE: manager/frontend/config/dev.env.js
================================================
'use strict'
const merge = require('webpack-merge')
const prodEnv = require('./prod.env')

module.exports = merge(prodEnv, {
  NODE_ENV: '"development"'
})


================================================
FILE: manager/frontend/config/index.js
================================================
'use strict'
// Template version: 1.3.1
// see http://vuejs-templates.github.io/webpack for documentation.

const path = require('path')

module.exports = {
  dev: {

    // Paths
    assetsSubDirectory: 'static',
    assetsPublicPath: './',
    proxyTable: {},

    // Various Dev Server settings
    host: 'localhost', // can be overwritten by process.env.HOST
    port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
    autoOpenBrowser: false,
    errorOverlay: true,
    notifyOnErrors: true,
    poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-

    
    /**
     * Source Maps
     */

    // https://webpack.js.org/configuration/devtool/#development
    devtool: 'cheap-module-eval-source-map',

    // If you have problems debugging vue-files in devtools,
    // set this to false - it *may* help
    // https://vue-loader.vuejs.org/en/options.html#cachebusting
    cacheBusting: true,

    cssSourceMap: true
  },

  build: {
    // Template for index.html
    index: path.resolve(__dirname, '../dist/index.html'),

    // Paths
    assetsRoot: path.resolve(__dirname, '../dist'),
    assetsSubDirectory: 'static',
    assetsPublicPath: './',

    /**
     * Source Maps
     */

    productionSourceMap: true,
    // https://webpack.js.org/configuration/devtool/#production
    devtool: '#source-map',

    // Gzip off by default as many popular static hosts such as
    // Surge or Netlify already gzip all static assets for you.
    // Before setting to `true`, make sure to:
    // npm install --save-dev compression-webpack-plugin
    productionGzip: false,
    productionGzipExtensions: ['js', 'css'],

    // Run the build command with an extra argument to
    // View the bundle analyzer report after build finishes:
    // `npm run build --report`
    // Set to `true` or `false` to always turn it on or off
    bundleAnalyzerReport: process.env.npm_config_report
  }
}


================================================
FILE: manager/frontend/config/prod.env.js
================================================
'use strict'
module.exports = {
  NODE_ENV: '"production"'
}


================================================
FILE: manager/frontend/index.html
================================================
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>Moyu Manager</title>
  </head>
  <body>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>


================================================
FILE: manager/frontend/package.json
================================================
{
  "name": "frontend",
  "version": "1.0.0",
  "description": "moyu manager",
  "author": "liuxu <i@liuxu.me>",
  "private": true,
  "scripts": {
    "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
    "start": "npm run dev",
    "build": "node build/build.js",
    "postbuild": "rm -rf ../backend/dist && mv dist ../backend/"
  },
  "dependencies": {
    "axios": "^1.3.2",
    "element-ui": "^2.15.12",
    "modern-normalize": "^1.1.0",
    "nes.css": "^2.3.0",
    "vue": "^2.5.2",
    "vue-cookies": "^1.8.2",
    "vue-router": "^3.0.1"
  },
  "devDependencies": {
    "autoprefixer": "^7.1.2",
    "babel-core": "^6.22.1",
    "babel-helper-vue-jsx-merge-props": "^2.0.3",
    "babel-loader": "^7.1.1",
    "babel-plugin-syntax-jsx": "^6.18.0",
    "babel-plugin-transform-runtime": "^6.22.0",
    "babel-plugin-transform-vue-jsx": "^3.5.0",
    "babel-preset-env": "^1.3.2",
    "babel-preset-stage-2": "^6.22.0",
    "chalk": "^2.0.1",
    "copy-webpack-plugin": "^4.0.1",
    "css-loader": "^0.28.0",
    "extract-text-webpack-plugin": "^3.0.0",
    "file-loader": "^1.1.4",
    "friendly-errors-webpack-plugin": "^1.6.1",
    "html-webpack-plugin": "^2.30.1",
    "node-notifier": "^5.1.2",
    "optimize-css-assets-webpack-plugin": "^3.2.0",
    "ora": "^1.2.0",
    "portfinder": "^1.0.13",
    "postcss-import": "^11.0.0",
    "postcss-loader": "^2.0.8",
    "postcss-url": "^7.2.1",
    "rimraf": "^2.6.0",
    "semver": "^5.3.0",
    "shelljs": "^0.7.6",
    "uglifyjs-webpack-plugin": "^1.1.1",
    "url-loader": "^0.5.8",
    "vue-loader": "^13.3.0",
    "vue-style-loader": "^3.0.1",
    "vue-template-compiler": "^2.5.2",
    "webpack": "^3.6.0",
    "webpack-bundle-analyzer": "^2.9.0",
    "webpack-dev-server": "^2.9.1",
    "webpack-merge": "^4.1.0"
  },
  "engines": {
    "node": ">= 6.0.0",
    "npm": ">= 3.0.0"
  },
  "browserslist": [
    "> 1%",
    "last 2 versions",
    "not ie <= 8"
  ]
}


================================================
FILE: manager/frontend/src/App.vue
================================================
<template>
  <div id="app">
    <router-view/>
  </div>
</template>

<script>
export default {
  name: 'App'
}
</script>

<style>
@import url('https://fonts.googleapis.com/css?family=Press+Start+2P');
#app {
  background: #FFFFFA;
}
</style>


================================================
FILE: manager/frontend/src/components/Login.vue
================================================
<template>
    <div class="nes-container with-title is-centered">
        <p class="title">Login</p>
        <div class="nes-field is-inline">
            <label for="inline_field">Username:</label>
            <input type="text" id="inline_field" class="nes-input is-success">
        </div>
        <div class="nes-field is-inline">
            <label for="inline_field">Password:</label>
            <input type="text" id="inline_field" class="nes-input is-success">
        </div>
    </div>
</template>
  
<script>
    export default {
        name: 'HelloWorld',
        data () {
            return {
            msg: 'Welcome to Your Vue.js App'
            }
        }
    }
</script>

<style scoped>
</style>

================================================
FILE: manager/frontend/src/components/MoyuFooter.vue
================================================
<template>
    <div class="moyu-footer">
        <p class="powered-by">Powered By: liuxu</p>
    </div>
</template>

<style scoped>
.moyu-footer {
    display: flex;
}
.powered-by {
    margin-left: auto;
    margin-right: 10px;
}
</style>

================================================
FILE: manager/frontend/src/components/MoyuNav.vue
================================================
<template>
    <div class="moyu-nav">
        <div class="nav-left">
            <div class="logo">
                <a href="/#/"><span>MoYu</span></a>
            </div>
        </div>
        <div class="nav-right">
            <div class="btn" v-if="logged">
                <button type="button" class="nes-btn" @click="logout">Logout</button>
            </div>
            <div class="btn" v-else>
                <button type="button" class="nes-btn is-primary" @click="login">Login</button>
            </div>
            <div class="github-logo">
                <a href="https://github.com/liuquanhao/moyu" target="_blank" rel="noopener">
                    <i class="nes-icon github is-medium"></i>
                </a>
            </div>
        </div>
    </div>
</template>

<script>
    export default {
        name: "moyu-nav",
        data: function() {
            return {
                isClick: true
            }
        },
        methods: {
            login: function() {
                this.$router.push('/login')
            },
            logout: function() {
                this.isClick = true
                this.axios.post('./api/logout', {}, {withCredentials: true}).then(res => {
                    this.$cookies.remove('uid')
                    this.isClick = false
                    if (this.$route.name == 'MainPage') {
                        this.$router.go(0)
                    } else {
                        this.$router.push('/')
                    }
                }).catch(err => {
                    this.$cookies.remove('uid')
                    this.isClick = false
                    if (this.$route.name == 'MainPage') {
                        this.$router.go(0)
                    } else {
                        this.$router.push('/')
                    }
                });
            }
        },
        computed: {
            logged: function() {
                return this.$cookies.get('uid')
            }
        }
    }
</script>

<style scoped>
.logo a {
    color: black;
    text-decoration: none;
}
.moyu-nav {
    display: flex;
    margin-top: 10px;
    margin-bottom: 10px;
    border-bottom: 4px solid #D3D3D3;
}
.nav-left {
    display: flex;
    width: 50%;
}
.nav-right {
    display: flex;
    justify-content: flex-end;
    width: 50%;
}
.logo {
    margin-left: 10px;
    font-size: 30px;
}
.btn {
    margin-right: 15px;
    margin-bottom: 5px;
}
.github-logo {
    margin-right: 10px;
}
</style>

================================================
FILE: manager/frontend/src/layouts/PageLayout.vue
================================================
<template>
    <el-container class="container">
        <el-header>
            <moyu-nav></moyu-nav>
        </el-header>
        <el-main>
            <slot></slot>
        </el-main>
        <el-footer>
            <moyu-footer></moyu-footer>
        </el-footer>
    </el-container>
</template>
  
<script>
    import MoyuNav from '../components/MoyuNav.vue'
    import MoyuFooter from '../components/MoyuFooter.vue'
    export default {
        components: {
            MoyuNav,
            MoyuFooter,
        },
        data: function() {
            return {}
        }
    }
</script>
  
<style scoped>
.container {
    min-width: 780px;
    max-width: 1260px;
    margin: 0px auto;
    justify-content: center;
}
.el-main {
    min-height: 800px;
}
</style>

================================================
FILE: manager/frontend/src/main.js
================================================
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'
import VueCookies from 'vue-cookies'

import "modern-normalize/modern-normalize.css"
import {
  Row,
  Col,
  Container,
  Header,
  Main,
  Footer,
  Table,
  TableColumn,
} from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import axios from 'axios'
import "nes.css/css/nes.min.css"

Vue.config.productionTip = false
Vue.prototype.axios = axios

Vue.component(Row.name, Row)
Vue.component(Col.name, Col)
Vue.component(Container.name, Container)
Vue.component(Header.name, Header)
Vue.component(Main.name, Main)
Vue.component(Footer.name, Footer)
Vue.component(Table.name, Table)
Vue.component(TableColumn.name, TableColumn)

Vue.use(VueCookies)

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  components: { App },
  template: '<App/>'
})


================================================
FILE: manager/frontend/src/pages/AddUrlPage.vue
================================================
<template>
    <page-layout>
        <div class="nes-container with-title">
            <p class="title">Add Page Url</p>
            <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>
            <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>
            <el-row>
                <el-col :span="6" :offset="6">
                    <button type="button" class="nes-btn test-btn" :class="[isClickTest ? 'is-disabled': 'is-warning']" @click="urlTest">Test</button>
                </el-col>
                <el-col :span="6" :offset="4">
                    <button type="button" class="nes-btn add-btn" :class="[isClickAdd ? 'is-disabled': 'is-primary']" @click="urlAdd">Add</button>
                </el-col>
            </el-row>
        </div>
    </page-layout>
</template>

<script>
    import PageLayout from '../layouts/PageLayout.vue'
    export default {
        components: {
            PageLayout,
        },
        data: function() {
            return {
                title: "",
                pageUrl: "",
                titleFail: false,
                pageUrlFail: false,
                pageUrlSuccess: false,
                isClickTest: false,
                isClickAdd: false,
            }
        },
        methods: {
            urlTest: function() {
                this.titleFail = false
                this.pageUrlFail = false
                if (!this.title) {
                    this.titleFail = true
                    return
                }
                if (!this.pageUrl) {
                    this.pageUrlFail = true
                    return
                }
                this.isClickTest = true
                this.axios.get(this.pageUrl, {withCredentials: false}).then(res => {
                    this.isClickTest = false
                    this.pageUrlSuccess = true
                }).catch(err => {
                    this.isClickTest = false
                    this.pageUrlFail = true
                });
            },
            urlAdd: function() {
                this.titleFail = false
                this.pageUrlFail = false
                if (!this.title) {
                    this.titleFail = true
                    return
                }
                if (!this.pageUrl) {
                    this.pageUrlFail = true
                    return
                }
                this.isClickAdd = true
                this.axios.post('./api/page_url', {
                    'title': this.title,
                    'page_url': this.pageUrl,
                }, {withCredentials: true}).then(res => {
                    this.$router.push('/')
                }).catch(err => {
                    this.isClickAdd = false
                    this.titleFail = true
                    this.pageUrlFail = true
                });
            }
        }
    }
</script>

<style scoped>
.nes-container {
    margin: 200px auto;
    width: 80%;
}
.nes-input {
    border-image-repeat: stretch;
}
.el-row {
    margin-bottom: 10px;
}
</style>

================================================
FILE: manager/frontend/src/pages/LoginPage.vue
================================================
<template>
    <page-layout>
        <div class="nes-container with-title is-centered">
            <p class="title">Login</p>
            <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>
            <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>
            <el-row>
                <el-col :span="6" :offset="8">
                    <button type="button" class="nes-btn login-btn" :class="[isClick ? 'is-disabled': 'is-primary']" @click="login">Login</button>
                </el-col>
                <el-col :span="8" class="forget-token-col">
                    <a href="https://github.com/liuquanhao/moyu" target="_blank" rel="noopener">
                        <span class="nes-text is-disabled forget-token-txt">Forget Token?</span>
                    </a>
                </el-col>
            </el-row>
        </div>
    </page-layout>
</template>

<script>
    import PageLayout from '../layouts/PageLayout.vue'
    export default {
        components: {
            PageLayout,
        },
        data: function() {
            return {
                user: "",
                token: "",
                userFail: false,
                tokenFail: false,
                isClick: false,
            }
        },
        methods: {
            login: function() {
                this.userFail = false
                this.tokenFail = false
                if (!this.user) {
                    this.userFail = true
                    return
                }
                if (!this.token) {
                    this.tokenFail = true
                    return
                }
                this.isClick = true
                this.axios.post('./api/login', {
                    user: this.user,
                    token: this.token
                }, {withCredentials: true}).then(res => {
                    this.isClick = false
                    this.$router.push('/')
                }).catch(err => {
                    this.isClick = false
                    this.userFail = true
                    this.tokenFail = true
                });
            }
        }
    }
</script>

<style scoped>
.nes-container {
    margin: 200px auto;
    width: 60%;
}
.el-row {
    margin-bottom: 10px;
}
.login-btn {
    margin-top: 10px;
}
.forget-token-col {
    margin-top: 30px;
}
.forget-token-txt {
    font-size: 5px;
    text-decoration: underline;
}
.nes-input {
    border-image-repeat: stretch;
}
</style>

================================================
FILE: manager/frontend/src/pages/MainPage.vue
================================================
<template>
    <page-layout>
        <div class="pages">
            <el-row type="flex">
                <el-col :span="4"><span>Title</span></el-col>
                <el-col :span="3"><span>Uptime</span></el-col>
                <el-col :span="3"><span>CPU</span></el-col>
                <el-col :span="3"><span>Memory</span></el-col>
                <el-col :span="3"><span>Disk(/)</span></el-col>
                <el-col :span="5"><span>BW(u|d)</span></el-col>
                <el-col :span="2"><button type="button" class="nes-btn" :class="[logged ? 'is-primary' : 'is-disabled']" @click="jumpTo('/add_url')">Add</button></el-col>
            </el-row>
            <el-row type="flex" v-for="page in pages" :key="page.id">
                <el-col :span="4" v-if="logged"><a :href="page.page_url" target="_blank" rel="noopener">{{ page.title }}</a></el-col>
                <el-col :span="4" v-else><span>{{ page.title }}</span></el-col>
                <el-col :span="3"><span v-if="page.page_data">{{ page.page_data.uptime | humanDur }}</span></el-col>
                <el-col :span="3"><span v-if="page.page_data" class="nes-text is-success">{{ page.page_data.cpu_percent | humanPerc }}%</span></el-col>
                <el-col :span="3"><span v-if="page.page_data" class="nes-text is-error">{{ page.page_data.memory_percent | humanPerc }}%</span></el-col>
                <el-col :span="3"><span v-if="page.page_data" class="nes-text is-primary">{{ page.page_data.disk_percent | humanPerc }}%</span></el-col>
                <el-col :span="5">
                    <div v-if="page.page_data" class="nes-badge is-splited net-badge">
                        <span class="is-dark">{{ page.page_data.net_send_rate | humanByte }}</span>
                        <span class="is-warning">{{ page.page_data.net_recv_rate | humanByte }}</span>
                    </div>
                </el-col>
                <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>
            </el-row>
        </div>
    </page-layout>
</template>

<script>
    import PageLayout from '../layouts/PageLayout.vue'
    export default {
        components: {
            PageLayout,
        },
        data: function() {
            return {
                pages: [],
            }
        },
        created: function() {
            this.initData()
            this.initWs()
        },
        beforeDestroy: function() {
            this.ws.close()
        },
        computed: {
            logged: function() {
                return this.$cookies.get('uid')
            },
        },
        methods: {
            jumpTo: function(url) {
                this.$router.push(url)
            },
            deletePage: function(id, event) {
                this.btnDisabledStatus(event.target, true, 'is-error', 'is-disabled')
                this.axios.delete('./api/page_url/' + id, {withCredentials: true})
                .then(res => {
                    this.btnDisabledStatus(event.target, false, 'is-disabled', 'is-error')
                    this.dropDataPage(id)
                    this.initWs()
                })
                .catch(err => {
                    this.btnDisabledStatus(event.target, false, 'is-disabled', 'is-warning')
                    console.log(err)
                })
            },
            dropDataPage(id) {
                this.pages = this.pages.filter(function(page) {
                    if (page.id == id) {
                        return false
                    }
                    return true
                })
            },
            btnDisabledStatus(element, disabled, removeClass, addClass) {
                element.disabled = disabled
                element.classList.remove(removeClass)
                element.classList.add(addClass)
            },
            initData() {
                this.axios.get('./api/page_url/', {withCredentials: true})
                .then(res => {
                    this.pages = res.data
                })
                .catch(err => {
                    console.log(err)
                })
            },
            initWs() {
                let wsProtocol = window.location.protocol == "https:" ? "wss://" : "ws://"
                let wsPort = window.location.port == "" ? "" : ":" + window.location.port
                this.ws = new WebSocket(wsProtocol + window.location.hostname + wsPort + window.location.pathname + "ws/page_data")
                this.ws.onopen = this.wsOnOpen
                this.ws.onerror = this.wsOnError
                this.ws.onmessage = this.wsOnMessage
                this.ws.onclose = this.wsOnClose
            },
            wsOnOpen() {
                console.log("ws connect success")
            },
            wsOnError() {
                console.log("ws connect fail")
                this.initWs()
            },
            wsOnMessage(e) {
                let pageInfo = JSON.parse(e.data)
                let idx = this.pages.findIndex(page => {
                    return page.id == pageInfo.page_id
                })
                if (idx < 0) {
                    return
                }
                let page = {}
                page.id = this.pages[idx].id
                page.title = this.pages[idx].title
                page.page_url = this.pages[idx].page_url
                pageInfo.page_data.net_send_rate = this.curNetSend(pageInfo.page_data.net_send, pageInfo.page_id, pageInfo.page_data.timestamp)
                pageInfo.page_data.net_recv_rate = this.curNetRecv(pageInfo.page_data.net_recv, pageInfo.page_id, pageInfo.page_data.timestamp)
                page.page_data = pageInfo.page_data
                this.$set(this.pages, idx, page)
            },
            wsOnClose(e) {
                console.log("ws close")
            },
            curNetSend: function(curSend, id, ts) {
                let lastSend = sessionStorage.getItem(id + "-lastSend")
                let lastSendTs = sessionStorage.getItem(id + "-lastSendTs")
                if (!lastSend) {
                    lastSend = curSend
                }
                sessionStorage.setItem(id + "-lastSend", curSend)
                sessionStorage.setItem(id + "-lastSendTs", ts)
                let dur = ts - lastSendTs
                if (dur < 1) {
                    return 0
                }
                return (curSend - lastSend)/(ts - lastSendTs)
            },
            curNetRecv: function(curRecv, id, ts) {
                let lastRecv = sessionStorage.getItem(id + "-lastRecv")
                let lastRecvTs = sessionStorage.getItem(id + "-lastRecvTs")
                if (!lastRecv) {
                    lastRecv = curRecv
                }
                sessionStorage.setItem(id + "-lastRecv", curRecv)
                sessionStorage.setItem(id + "-lastRecvTs", ts)
                let dur = ts - lastRecvTs
                if (dur < 1) {
                    return 0
                }
                return (curRecv - lastRecv)/(ts - lastRecvTs)
            },
        },
        filters: {
            humanByte: function (size, num) {
                if (size < 1024 * 1024) {
                    return Math.trunc(size / 1024).toFixed(num) + "K"
                } else if (size < 1024 * 1024 * 1024) {
                    return (size / 1024 / 1024).toFixed(num) + "M"
                } else {
                    return (size / 1024 / 1024 / 1024).toFixed(num) + "G"
                }
            },
            humanDur: function (sec) {
                if (sec < 60) {
                    return Math.trunc(sec) + " s";
                } else if (sec < 3600) {
                    let min = Math.trunc(sec / 60);
                    return min + " m"
                } else if (sec < 86400) {
                    let hour = Math.trunc(sec / 3600);
                    return hour + " h"
                } else {
                    let day = Math.trunc(sec / 86400);
                    return day + "d"
                }
            },
            humanPerc: function (float) {
                if (!float) {
                    return 0
                }
                return float.toFixed(1)
            },
        }
    }
</script>

<style scoped>
.pages {
    margin: 20px 10px 0px 10px;
}
.el-row {
    border-bottom: 1px;
    border-bottom-style: solid;
    align-items: center;
}
.el-col {
    margin-top: 10px;
    margin-bottom: 10px;
    text-align: center;
}
.el-col button {
    display: block;
    margin: auto;
}
</style>

================================================
FILE: manager/frontend/src/router/index.js
================================================
import Vue from 'vue'
import Router from 'vue-router'
import LoginPage from '@/pages/LoginPage'
import MainPage from '@/pages/MainPage'
import AddUrlPage from '@/pages/AddUrlPage'

Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',
      name: 'MainPage',
      component: MainPage
    },
    {
      path: '/login',
      name: 'LoginPage',
      component: LoginPage
    },
    {
      path: '/add_url',
      name: 'AddUrlPage',
      component: AddUrlPage
    }
  ]
})


================================================
FILE: manager/frontend/static/.gitkeep
================================================


================================================
FILE: page/backend/.gitignore
================================================
dist/
moyu-page

================================================
FILE: page/backend/controller/system.go
================================================
package controller

import (
	"time"

	"github.com/gofiber/fiber/v2"
	"github.com/gofiber/websocket/v2"
	"github.com/liuquanhao/moyu/service"
)

func GetSysInfo(c *fiber.Ctx) error {
	return c.JSON(service.GetSystemInfo())
}

func PushSysStatus(c *websocket.Conn) {
	for {
		c.WriteJSON(service.GetSystemStatus())
		time.Sleep(1 * time.Second)
	}
}

func GetPageData(c *fiber.Ctx) error {
	return c.JSON(service.GetPageData())
}


================================================
FILE: page/backend/go.mod
================================================
module github.com/liuquanhao/moyu

go 1.19

require (
	github.com/bytedance/sonic v1.5.0
	github.com/gofiber/fiber/v2 v2.40.1
	github.com/gofiber/websocket/v2 v2.1.2
	github.com/shirou/gopsutil/v3 v3.22.10
)

require (
	github.com/andybalholm/brotli v1.0.4 // indirect
	github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06 // indirect
	github.com/fasthttp/websocket v1.5.0 // indirect
	github.com/go-ole/go-ole v1.2.6 // indirect
	github.com/klauspost/compress v1.15.9 // indirect
	github.com/klauspost/cpuid/v2 v2.0.9 // indirect
	github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
	github.com/mattn/go-colorable v0.1.13 // indirect
	github.com/mattn/go-isatty v0.0.16 // indirect
	github.com/mattn/go-runewidth v0.0.14 // indirect
	github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
	github.com/rivo/uniseg v0.2.0 // indirect
	github.com/savsgio/gotils v0.0.0-20211223103454-d0aaa54c5899 // indirect
	github.com/tklauser/go-sysconf v0.3.11 // indirect
	github.com/tklauser/numcpus v0.6.0 // indirect
	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
	github.com/valyala/bytebufferpool v1.0.0 // indirect
	github.com/valyala/fasthttp v1.41.0 // indirect
	github.com/valyala/tcplisten v1.0.0 // indirect
	github.com/yusufpapurcu/wmi v1.2.2 // indirect
	golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
	golang.org/x/sys v0.2.0 // indirect
)


================================================
FILE: page/backend/go.sum
================================================
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/bytedance/sonic v1.5.0 h1:XWdTi8bwPgxIML+eNV1IwNuTROK6EUrQ65ey8yd6fRQ=
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06 h1:1sDoSuDPWzhkdzNVxCxtIaKiAe96ESVPv8coGwc1gZ4=
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fasthttp/websocket v1.5.0 h1:B4zbe3xXyvIdnqjOZrafVFklCUq5ZLo/TqCt5JA1wLE=
github.com/fasthttp/websocket v1.5.0/go.mod h1:n0BlOQvJdPbTuBkZT0O5+jk/sp/1/VCzquR1BehI2F4=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/gofiber/fiber/v2 v2.40.1 h1:pc7n9VVpGIqNsvg9IPLQhyFEMJL8gCs1kneH5D1pIl4=
github.com/gofiber/fiber/v2 v2.40.1/go.mod h1:Gko04sLksnHbzLSRBFWPFdzM9Ws9pRxvvIaohJK1dsk=
github.com/gofiber/websocket/v2 v2.1.2 h1:EulKyLB/fJgui5+6c8irwEnYQ9FRsrLZfkrq9OfTDGc=
github.com/gofiber/websocket/v2 v2.1.2/go.mod h1:S+sKWo0xeC7Wnz5h4/8f6D/NxsrLFIdWDYB3SyVO9pE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/klauspost/compress v1.14.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY=
github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/savsgio/gotils v0.0.0-20211223103454-d0aaa54c5899 h1:Orn7s+r1raRTBKLSc9DmbktTT04sL+vkzsbRD2Q8rOI=
github.com/savsgio/gotils v0.0.0-20211223103454-d0aaa54c5899/go.mod h1:oejLrk1Y/5zOF+c/aHtXqn3TFlzzbAgPWg8zBiAHDas=
github.com/shirou/gopsutil/v3 v3.22.10 h1:4KMHdfBRYXGF9skjDWiL4RA2N+E8dRdodU/bOZpPoVg=
github.com/shirou/gopsutil/v3 v3.22.10/go.mod h1:QNza6r4YQoydyCfo6rH0blGfKahgibh4dQmV5xdFkQk=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk=
github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI=
github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ=
github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms=
github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.33.0/go.mod h1:KJRK/MXx0J+yd0c5hlR+s1tIHD72sniU8ZJjl97LIw4=
github.com/valyala/fasthttp v1.41.0 h1:zeR0Z1my1wDHTRiamBCXVglQdbUwgb9uWG3k1HQz6jY=
github.com/valyala/fasthttp v1.41.0/go.mod h1:f6VbjjoI3z1NDOZOv17o6RvtRSWxC77seBFc2uWtgiY=
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220111093109-d55c255bac03/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220906165146-f3363e06e74c/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=


================================================
FILE: page/backend/main.go
================================================
package main

import (
	"embed"
	"io/fs"
	"log"
	"net/http"
	"os"

	"github.com/bytedance/sonic"
	"github.com/gofiber/fiber/v2"
	"github.com/gofiber/fiber/v2/middleware/cors"
	"github.com/gofiber/fiber/v2/middleware/filesystem"
	"github.com/gofiber/websocket/v2"
	"github.com/liuquanhao/moyu/controller"
	"github.com/liuquanhao/moyu/middleware"
)

//go:embed dist
var frontend embed.FS

func main() {
	app := fiber.New(fiber.Config{
		JSONEncoder: sonic.Marshal,
		JSONDecoder: sonic.Unmarshal,
	})
	app.Use(cors.New())
	base := app.Group(os.Getenv("BASEURL"))
	ws := base.Group("/ws")
	ws.Use("/*", middleware.UpgradeOptions)
	ws.Get("/sys_status", websocket.New(controller.PushSysStatus))

	api := base.Group("/api")
	api.Get("/sys_info", controller.GetSysInfo)
	api.Get("/page_data", controller.GetPageData)

	stripped, err := fs.Sub(frontend, "dist")
	if err != nil {
		log.Fatal(err)
	}
	base.Use("/", filesystem.New(filesystem.Config{
		Root:   http.FS(stripped),
		Browse: true,
	}))

	host := os.Getenv("HOST")
	port := os.Getenv("PORT")
	log.Println(host + ":" + port)
	log.Fatal(app.Listen(host + ":" + port))
}


================================================
FILE: page/backend/middleware/ws_options.go
================================================
package middleware

import (
	"github.com/gofiber/fiber/v2"
	"github.com/gofiber/websocket/v2"
)

func UpgradeOptions(c *fiber.Ctx) error {
	if websocket.IsWebSocketUpgrade(c) {
		return c.Next()
	}
	return fiber.ErrUpgradeRequired
}


================================================
FILE: page/backend/service/cpu.go
================================================
package service

import "github.com/shirou/gopsutil/v3/cpu"

type Core struct {
	CPU       int32    `json:"cpu"`
	CoreID    string   `json:"core_id"`
	ModelName string   `json:"model"`
	Mhz       float64  `json:"mhz"`
	Flags     []string `json:"flags"`
}
type CPUInfo struct {
	Count int     `json:"count"`
	Cores []*Core `json:"cores"`
}

func (cpuInfo *CPUInfo) AppendCore(core *Core) {
	cpuInfo.Cores = append(cpuInfo.Cores, core)
}

func GetCPUInfo() *CPUInfo {
	cpuInfoStats, _ := cpu.Info()
	cpuInfo := new(CPUInfo)
	cpuInfo.Count, _ = cpu.Counts(true)
	for _, infoStat := range cpuInfoStats {
		core := &Core{
			CPU:       infoStat.CPU,
			CoreID:    infoStat.CoreID,
			ModelName: infoStat.ModelName,
			Mhz:       infoStat.Mhz,
			Flags:     infoStat.Flags,
		}
		cpuInfo.AppendCore(core)
	}
	return cpuInfo
}


================================================
FILE: page/backend/service/disk.go
================================================
package service

import (
	"strings"

	"github.com/shirou/gopsutil/v3/disk"
)

type Partition struct {
	Device      string  `json:"device"`
	MountPoint  string  `json:"mount_point"`
	Size        uint64  `json:"size"`
	UsedPercent float64 `json:"used_percent"`
}
type DiskInfo struct {
	Partitions []*Partition `json:"partitions"`
}

func (diskInfo *DiskInfo) AppendPartition(partition *Partition) {
	diskInfo.Partitions = append(diskInfo.Partitions, partition)
}

func GetDiskInfo() *DiskInfo {
	diskInfo := new(DiskInfo)
	partitionStats, _ := disk.Partitions(false)
	for _, partitionStat := range partitionStats {
		if strings.HasPrefix(partitionStat.Mountpoint, "/snap") || strings.HasPrefix(partitionStat.Mountpoint, "/loop") {
			continue
		}
		usageStat, err := disk.Usage(partitionStat.Mountpoint)
		if err != nil {
			continue
		}
		partition := &Partition{
			Device:      partitionStat.Device,
			MountPoint:  partitionStat.Mountpoint,
			Size:        usageStat.Total,
			UsedPercent: usageStat.UsedPercent,
		}
		diskInfo.AppendPartition(partition)
	}
	return diskInfo
}


================================================
FILE: page/backend/service/host.go
================================================
package service

import (
	"github.com/shirou/gopsutil/v3/host"
)

type HostInfo struct {
	Hostname        string `json:"hostname"`
	Distribution    string `json:"distribution"`
	Arch            string `json:"arch"`
	Kernel          string `json:"kernel"`
	VirtualPlatform string `json:"virtual_platform"`
	Uptime          uint64 `json:"uptime"`
}

func GetHostInfo() *HostInfo {
	hostInfo, _ := host.Info()
	return &HostInfo{
		Hostname:        hostInfo.Hostname,
		Distribution:    hostInfo.Platform + " " + hostInfo.PlatformVersion,
		Arch:            hostInfo.KernelArch,
		Kernel:          hostInfo.KernelVersion,
		VirtualPlatform: hostInfo.VirtualizationSystem,
		Uptime:          hostInfo.Uptime,
	}
}

func GetUptime() uint64 {
	hostInfo, _ := host.Info()
	return hostInfo.Uptime
}


================================================
FILE: page/backend/service/memory.go
================================================
package service

import (
	"github.com/shirou/gopsutil/v3/mem"
)

type MemoryInfo struct {
	Memory uint64 `json:"memory"`
	Swap   uint64 `json:"swap"`
}

type MemoryStatus struct {
	MemoryPercent float64 `json:"memory_percent"`
	SwapPercent   float64 `json:"swap_percent"`
}

func GetMemoryInfo() *MemoryInfo {
	vmStat, _ := mem.VirtualMemory()
	swapStat, _ := mem.SwapMemory()
	return &MemoryInfo{
		Memory: vmStat.Total,
		Swap:   swapStat.Total,
	}
}

func GetMemoryStatus() *MemoryStatus {
	memInfo, _ := mem.VirtualMemory()
	swapInfo, _ := mem.SwapMemory()
	return &MemoryStatus{
		MemoryPercent: memInfo.UsedPercent,
		SwapPercent:   swapInfo.UsedPercent,
	}
}


================================================
FILE: page/backend/service/network.go
================================================
package service

import (
	"regexp"

	"github.com/shirou/gopsutil/v3/net"
)

type Ifce struct {
	Name     string `json:"name"`
	ByteSend uint64 `json:"send_byte"`
	ByteRecv uint64 `json:"recv_byte"`
}

type NetworkInfo struct {
	Ifces []*Ifce `json:"ifces"`
}

func (networkInfo *NetworkInfo) AppendIfce(ifce *Ifce) {
	networkInfo.Ifces = append(networkInfo.Ifces, ifce)
}

func GetNetworkInfo() *NetworkInfo {
	networkInfo := new(NetworkInfo)
	ioStats, _ := net.IOCounters(true)
	r, _ := regexp.Compile("^(eth|enp).*")
	for _, ioStat := range ioStats {
		if !r.MatchString(ioStat.Name) {
			continue
		}
		ifce := &Ifce{
			Name:     ioStat.Name,
			ByteSend: ioStat.BytesSent,
			ByteRecv: ioStat.BytesRecv,
		}
		networkInfo.AppendIfce(ifce)
	}
	return networkInfo
}


================================================
FILE: page/backend/service/page_data.go
================================================
package service

import (
	"time"

	"github.com/shirou/gopsutil/v3/cpu"
	"github.com/shirou/gopsutil/v3/disk"
	"github.com/shirou/gopsutil/v3/host"
	"github.com/shirou/gopsutil/v3/mem"
	"github.com/shirou/gopsutil/v3/net"
)

type PageData struct {
	Uptime        uint64  `json:"uptime"`
	CPUPercent    float64 `json:"cpu_percent"`
	MemoryPercent float64 `json:"memory_percent"`
	DiskPercent   float64 `json:"disk_percent"`
	NetSendTotal  uint64  `json:"net_send"`
	NetRecvTotal  uint64  `json:"net_recv"`
	Timestamp     int64   `json:"timestamp"`
}

func GetPageData() *PageData {
	hostInfo, _ := host.Info()
	cpuPercent, _ := cpu.Percent(time.Duration(1)*time.Second, false)
	memInfo, _ := mem.VirtualMemory()
	diskUsage, _ := disk.Usage("/")
	ioStats, _ := net.IOCounters(false)
	return &PageData{
		Uptime:        hostInfo.Uptime,
		CPUPercent:    cpuPercent[0],
		MemoryPercent: memInfo.UsedPercent,
		DiskPercent:   diskUsage.UsedPercent,
		NetSendTotal:  ioStats[0].BytesSent,
		NetRecvTotal:  ioStats[0].BytesRecv,
		Timestamp:     time.Now().Unix(),
	}
}


================================================
FILE: page/backend/service/system.go
================================================
package service

import (
	"time"

	"github.com/shirou/gopsutil/v3/cpu"
	"github.com/shirou/gopsutil/v3/load"
)

type SystemInfo struct {
	HostInfo     *HostInfo     `json:"host_info"`
	CPUInfo      *CPUInfo      `json:"cpu_info"`
	MemoryInfo   *MemoryInfo   `json:"memory_info"`
	NetworkInfo  *NetworkInfo  `json:"network_info"`
	DiskInfo     *DiskInfo     `json:"disk_info"`
	SystemStatus *SystemStatus `json:"system_status"`
}

type SystemStatus struct {
	CPUPercent   []float64     `json:"cpu_percent"`
	LoadAvg      *load.AvgStat `json:"load_avg"`
	MemoryStatus *MemoryStatus `json:"memory_status"`
	DiskInfo     *DiskInfo     `json:"disk_info"`
	NetworkInfo  *NetworkInfo  `json:"network_info"`
	Uptime       uint64        `json:"uptime"`
	Timestamp    int64         `json:"timestamp"`
}

func GetSystemInfo() *SystemInfo {
	return &SystemInfo{
		HostInfo:     GetHostInfo(),
		CPUInfo:      GetCPUInfo(),
		MemoryInfo:   GetMemoryInfo(),
		DiskInfo:     GetDiskInfo(),
		NetworkInfo:  GetNetworkInfo(),
		SystemStatus: GetSystemStatus(),
	}
}

func GetSystemStatus() *SystemStatus {
	cpuPercent, _ := cpu.Percent(time.Duration(1)*time.Second, true)
	loadAvg, _ := load.Avg()
	return &SystemStatus{
		CPUPercent:   cpuPercent,
		LoadAvg:      loadAvg,
		MemoryStatus: GetMemoryStatus(),
		DiskInfo:     GetDiskInfo(),
		NetworkInfo:  GetNetworkInfo(),
		Uptime:       GetUptime(),
		Timestamp:    time.Now().Unix(),
	}
}


================================================
FILE: page/frontend/.babelrc
================================================
{
  "presets": [["es2015", { "modules": false }]],
  "plugins": [
    [
      "component",
      {
        "libraryName": "element-ui",
        "styleLibraryName": "theme-chalk"
      }
    ]
  ]
}

================================================
FILE: page/frontend/.editorconfig
================================================
root = true

[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true


================================================
FILE: page/frontend/.gitignore
================================================
.DS_Store
node_modules/
/dist/
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln


================================================
FILE: page/frontend/.postcssrc.js
================================================
// https://github.com/michael-ciniawsky/postcss-load-config

module.exports = {
  "plugins": {
    "postcss-import": {},
    "postcss-url": {},
    // to edit target browsers: use "browserslist" field in package.json
    "autoprefixer": {}
  }
}


================================================
FILE: page/frontend/README.md
================================================
# frontend

> A Vue.js project

## Build Setup

``` bash
# install dependencies
npm install

# serve with hot reload at localhost:8080
npm run dev

# build for production with minification
npm run build

# build for production and view the bundle analyzer report
npm run build --report
```

For 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).


================================================
FILE: page/frontend/build/build.js
================================================
'use strict'
require('./check-versions')()

process.env.NODE_ENV = 'production'

const ora = require('ora')
const rm = require('rimraf')
const path = require('path')
const chalk = require('chalk')
const webpack = require('webpack')
const config = require('../config')
const webpackConfig = require('./webpack.prod.conf')

const spinner = ora('building for production...')
spinner.start()

rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
  if (err) throw err
  webpack(webpackConfig, (err, stats) => {
    spinner.stop()
    if (err) throw err
    process.stdout.write(stats.toString({
      colors: true,
      modules: false,
      children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build.
      chunks: false,
      chunkModules: false
    }) + '\n\n')

    if (stats.hasErrors()) {
      console.log(chalk.red('  Build failed with errors.\n'))
      process.exit(1)
    }

    console.log(chalk.cyan('  Build complete.\n'))
    console.log(chalk.yellow(
      '  Tip: built files are meant to be served over an HTTP server.\n' +
      '  Opening index.html over file:// won\'t work.\n'
    ))
  })
})


================================================
FILE: page/frontend/build/check-versions.js
================================================
'use strict'
const chalk = require('chalk')
const semver = require('semver')
const packageConfig = require('../package.json')
const shell = require('shelljs')

function exec (cmd) {
  return require('child_process').execSync(cmd).toString().trim()
}

const versionRequirements = [
  {
    name: 'node',
    currentVersion: semver.clean(process.version),
    versionRequirement: packageConfig.engines.node
  }
]

if (shell.which('npm')) {
  versionRequirements.push({
    name: 'npm',
    currentVersion: exec('npm --version'),
    versionRequirement: packageConfig.engines.npm
  })
}

module.exports = function () {
  const warnings = []

  for (let i = 0; i < versionRequirements.length; i++) {
    const mod = versionRequirements[i]

    if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
      warnings.push(mod.name + ': ' +
        chalk.red(mod.currentVersion) + ' should be ' +
        chalk.green(mod.versionRequirement)
      )
    }
  }

  if (warnings.length) {
    console.log('')
    console.log(chalk.yellow('To use this template, you must update following to modules:'))
    console.log()

    for (let i = 0; i < warnings.length; i++) {
      const warning = warnings[i]
      console.log('  ' + warning)
    }

    console.log()
    process.exit(1)
  }
}


================================================
FILE: page/frontend/build/utils.js
================================================
'use strict'
const path = require('path')
const config = require('../config')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const packageConfig = require('../package.json')

exports.assetsPath = function (_path) {
  const assetsSubDirectory = process.env.NODE_ENV === 'production'
    ? config.build.assetsSubDirectory
    : config.dev.assetsSubDirectory

  return path.posix.join(assetsSubDirectory, _path)
}

exports.cssLoaders = function (options) {
  options = options || {}

  const cssLoader = {
    loader: 'css-loader',
    options: {
      sourceMap: options.sourceMap
    }
  }

  const postcssLoader = {
    loader: 'postcss-loader',
    options: {
      sourceMap: options.sourceMap
    }
  }

  // generate loader string to be used with extract text plugin
  function generateLoaders (loader, loaderOptions) {
    const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader]

    if (loader) {
      loaders.push({
        loader: loader + '-loader',
        options: Object.assign({}, loaderOptions, {
          sourceMap: options.sourceMap
        })
      })
    }

    // Extract CSS when that option is specified
    // (which is the case during production build)
    if (options.extract) {
      return ExtractTextPlugin.extract({
        use: loaders,
        fallback: 'vue-style-loader'
      })
    } else {
      return ['vue-style-loader'].concat(loaders)
    }
  }

  // https://vue-loader.vuejs.org/en/configurations/extract-css.html
  return {
    css: generateLoaders(),
    postcss: generateLoaders(),
    less: generateLoaders('less'),
    sass: generateLoaders('sass', { indentedSyntax: true }),
    scss: generateLoaders('sass'),
    stylus: generateLoaders('stylus'),
    styl: generateLoaders('stylus')
  }
}

// Generate loaders for standalone style files (outside of .vue)
exports.styleLoaders = function (options) {
  const output = []
  const loaders = exports.cssLoaders(options)

  for (const extension in loaders) {
    const loader = loaders[extension]
    output.push({
      test: new RegExp('\\.' + extension + '$'),
      use: loader
    })
  }

  return output
}

exports.createNotifierCallback = () => {
  const notifier = require('node-notifier')

  return (severity, errors) => {
    if (severity !== 'error') return

    const error = errors[0]
    const filename = error.file && error.file.split('!').pop()

    notifier.notify({
      title: packageConfig.name,
      message: severity + ': ' + error.name,
      subtitle: filename || '',
      icon: path.join(__dirname, 'logo.png')
    })
  }
}


================================================
FILE: page/frontend/build/vue-loader.conf.js
================================================
'use strict'
const utils = require('./utils')
const config = require('../config')
const isProduction = process.env.NODE_ENV === 'production'
const sourceMapEnabled = isProduction
  ? config.build.productionSourceMap
  : config.dev.cssSourceMap

module.exports = {
  loaders: utils.cssLoaders({
    sourceMap: sourceMapEnabled,
    extract: isProduction
  }),
  cssSourceMap: sourceMapEnabled,
  cacheBusting: config.dev.cacheBusting,
  transformToRequire: {
    video: ['src', 'poster'],
    source: 'src',
    img: 'src',
    image: 'xlink:href'
  }
}


================================================
FILE: page/frontend/build/webpack.base.conf.js
================================================
'use strict'
const path = require('path')
const utils = require('./utils')
const config = require('../config')
const vueLoaderConfig = require('./vue-loader.conf')

function resolve (dir) {
  return path.join(__dirname, '..', dir)
}



module.exports = {
  context: path.resolve(__dirname, '../'),
  entry: {
    app: './src/main.js'
  },
  output: {
    path: config.build.assetsRoot,
    filename: '[name].js',
    publicPath: process.env.NODE_ENV === 'production'
      ? config.build.assetsPublicPath
      : config.dev.assetsPublicPath
  },
  resolve: {
    extensions: ['.js', '.vue', '.json'],
    alias: {
      'vue$': 'vue/dist/vue.esm.js',
      '@': resolve('src'),
    }
  },
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: vueLoaderConfig
      },
      {
        test: /\.js$/,
        loader: 'babel-loader',
        include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]
      },
      {
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: utils.assetsPath('img/[name].[hash:7].[ext]')
        }
      },
      {
        test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: utils.assetsPath('media/[name].[hash:7].[ext]')
        }
      },
      {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
        }
      }
    ]
  },
  node: {
    // prevent webpack from injecting useless setImmediate polyfill because Vue
    // source contains it (although only uses it if it's native).
    setImmediate: false,
    // prevent webpack from injecting mocks to Node native modules
    // that does not make sense for the client
    dgram: 'empty',
    fs: 'empty',
    net: 'empty',
    tls: 'empty',
    child_process: 'empty'
  }
}


================================================
FILE: page/frontend/build/webpack.dev.conf.js
================================================
'use strict'
const utils = require('./utils')
const webpack = require('webpack')
const config = require('../config')
const merge = require('webpack-merge')
const path = require('path')
const baseWebpackConfig = require('./webpack.base.conf')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
const portfinder = require('portfinder')

const HOST = process.env.HOST
const PORT = process.env.PORT && Number(process.env.PORT)

const devWebpackConfig = merge(baseWebpackConfig, {
  module: {
    rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true })
  },
  // cheap-module-eval-source-map is faster for development
  devtool: config.dev.devtool,

  // these devServer options should be customized in /config/index.js
  devServer: {
    clientLogLevel: 'warning',
    historyApiFallback: {
      rewrites: [
        { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') },
      ],
    },
    hot: true,
    contentBase: false, // since we use CopyWebpackPlugin.
    compress: true,
    host: HOST || config.dev.host,
    port: PORT || config.dev.port,
    open: config.dev.autoOpenBrowser,
    overlay: config.dev.errorOverlay
      ? { warnings: false, errors: true }
      : false,
    publicPath: config.dev.assetsPublicPath,
    proxy: config.dev.proxyTable,
    quiet: true, // necessary for FriendlyErrorsPlugin
    watchOptions: {
      poll: config.dev.poll,
    }
  },
  plugins: [
    new webpack.DefinePlugin({
      'process.env': require('../config/dev.env')
    }),
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update.
    new webpack.NoEmitOnErrorsPlugin(),
    // https://github.com/ampedandwired/html-webpack-plugin
    new HtmlWebpackPlugin({
      filename: 'index.html',
      template: 'index.html',
      inject: true
    }),
    // copy custom static assets
    new CopyWebpackPlugin([
      {
        from: path.resolve(__dirname, '../static'),
        to: config.dev.assetsSubDirectory,
        ignore: ['.*']
      }
    ])
  ]
})

module.exports = new Promise((resolve, reject) => {
  portfinder.basePort = process.env.PORT || config.dev.port
  portfinder.getPort((err, port) => {
    if (err) {
      reject(err)
    } else {
      // publish the new Port, necessary for e2e tests
      process.env.PORT = port
      // add port to devServer config
      devWebpackConfig.devServer.port = port

      // Add FriendlyErrorsPlugin
      devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({
        compilationSuccessInfo: {
          messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`],
        },
        onErrors: config.dev.notifyOnErrors
        ? utils.createNotifierCallback()
        : undefined
      }))

      resolve(devWebpackConfig)
    }
  })
})


================================================
FILE: page/frontend/build/webpack.prod.conf.js
================================================
'use strict'
const path = require('path')
const utils = require('./utils')
const webpack = require('webpack')
const config = require('../config')
const merge = require('webpack-merge')
const baseWebpackConfig = require('./webpack.base.conf')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')

const env = require('../config/prod.env')

const webpackConfig = merge(baseWebpackConfig, {
  module: {
    rules: utils.styleLoaders({
      sourceMap: config.build.productionSourceMap,
      extract: true,
      usePostCSS: true
    })
  },
  devtool: config.build.productionSourceMap ? config.build.devtool : false,
  output: {
    path: config.build.assetsRoot,
    filename: utils.assetsPath('js/[name].[chunkhash].js'),
    chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
  },
  plugins: [
    // http://vuejs.github.io/vue-loader/en/workflow/production.html
    new webpack.DefinePlugin({
      'process.env': env
    }),
    new UglifyJsPlugin({
      uglifyOptions: {
        compress: {
          warnings: false
        }
      },
      sourceMap: config.build.productionSourceMap,
      parallel: true
    }),
    // extract css into its own file
    new ExtractTextPlugin({
      filename: utils.assetsPath('css/[name].[contenthash].css'),
      // Setting the following option to `false` will not extract CSS from codesplit chunks.
      // Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack.
      // It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`, 
      // increasing file size: https://github.com/vuejs-templates/webpack/issues/1110
      allChunks: true,
    }),
    // Compress extracted CSS. We are using this plugin so that possible
    // duplicated CSS from different components can be deduped.
    new OptimizeCSSPlugin({
      cssProcessorOptions: config.build.productionSourceMap
        ? { safe: true, map: { inline: false } }
        : { safe: true }
    }),
    // generate dist index.html with correct asset hash for caching.
    // you can customize output by editing /index.html
    // see https://github.com/ampedandwired/html-webpack-plugin
    new HtmlWebpackPlugin({
      filename: config.build.index,
      template: 'index.html',
      inject: true,
      minify: {
        removeComments: true,
        collapseWhitespace: true,
        removeAttributeQuotes: true
        // more options:
        // https://github.com/kangax/html-minifier#options-quick-reference
      },
      // necessary to consistently work with multiple chunks via CommonsChunkPlugin
      chunksSortMode: 'dependency'
    }),
    // keep module.id stable when vendor modules does not change
    new webpack.HashedModuleIdsPlugin(),
    // enable scope hoisting
    new webpack.optimize.ModuleConcatenationPlugin(),
    // split vendor js into its own file
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
      minChunks (module) {
        // any required modules inside node_modules are extracted to vendor
        return (
          module.resource &&
          /\.js$/.test(module.resource) &&
          module.resource.indexOf(
            path.join(__dirname, '../node_modules')
          ) === 0
        )
      }
    }),
    // extract webpack runtime and module manifest to its own file in order to
    // prevent vendor hash from being updated whenever app bundle is updated
    new webpack.optimize.CommonsChunkPlugin({
      name: 'manifest',
      minChunks: Infinity
    }),
    // This instance extracts shared chunks from code splitted chunks and bundles them
    // in a separate chunk, similar to the vendor chunk
    // see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk
    new webpack.optimize.CommonsChunkPlugin({
      name: 'app',
      async: 'vendor-async',
      children: true,
      minChunks: 3
    }),

    // copy custom static assets
    new CopyWebpackPlugin([
      {
        from: path.resolve(__dirname, '../static'),
        to: config.build.assetsSubDirectory,
        ignore: ['.*']
      }
    ])
  ]
})

if (config.build.productionGzip) {
  const CompressionWebpackPlugin = require('compression-webpack-plugin')

  webpackConfig.plugins.push(
    new CompressionWebpackPlugin({
      asset: '[path].gz[query]',
      algorithm: 'gzip',
      test: new RegExp(
        '\\.(' +
        config.build.productionGzipExtensions.join('|') +
        ')$'
      ),
      threshold: 10240,
      minRatio: 0.8
    })
  )
}

if (config.build.bundleAnalyzerReport) {
  const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
  webpackConfig.plugins.push(new BundleAnalyzerPlugin())
}

module.exports = webpackConfig


================================================
FILE: page/frontend/config/dev.env.js
================================================
'use strict'
const merge = require('webpack-merge')
const prodEnv = require('./prod.env')

module.exports = merge(prodEnv, {
  NODE_ENV: '"development"',
})


================================================
FILE: page/frontend/config/index.js
================================================
'use strict'
// Template version: 1.3.1
// see http://vuejs-templates.github.io/webpack for documentation.

const path = require('path')

module.exports = {
  dev: {

    // Paths
    assetsSubDirectory: 'static',
    assetsPublicPath: './',
    proxyTable: {},

    // Various Dev Server settings
    host: 'localhost', // can be overwritten by process.env.HOST
    port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
    autoOpenBrowser: false,
    errorOverlay: true,
    notifyOnErrors: true,
    poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-

    
    /**
     * Source Maps
     */

    // https://webpack.js.org/configuration/devtool/#development
    devtool: 'cheap-module-eval-source-map',

    // If you have problems debugging vue-files in devtools,
    // set this to false - it *may* help
    // https://vue-loader.vuejs.org/en/options.html#cachebusting
    cacheBusting: true,

    cssSourceMap: true
  },

  build: {
    // Template for index.html
    index: path.resolve(__dirname, '../dist/index.html'),

    // Paths
    assetsRoot: path.resolve(__dirname, '../dist'),
    assetsSubDirectory: 'static',
    assetsPublicPath: './',

    /**
     * Source Maps
     */

    productionSourceMap: true,
    // https://webpack.js.org/configuration/devtool/#production
    devtool: '#source-map',

    // Gzip off by default as many popular static hosts such as
    // Surge or Netlify already gzip all static assets for you.
    // Before setting to `true`, make sure to:
    // npm install --save-dev compression-webpack-plugin
    productionGzip: false,
    productionGzipExtensions: ['js', 'css'],

    // Run the build command with an extra argument to
    // View the bundle analyzer report after build finishes:
    // `npm run build --report`
    // Set to `true` or `false` to always turn it on or off
    bundleAnalyzerReport: process.env.npm_config_report
  }
}


================================================
FILE: page/frontend/config/prod.env.js
================================================
'use strict'
module.exports = {
  NODE_ENV: '"production"'
}


================================================
FILE: page/frontend/index.html
================================================
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" id="viewport" content="width=device-width,initial-scale=1.0, user-scalable=yes">
    <title>Moyu Page</title>
  </head>
  <body>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
  <script>
     let ua = navigator.userAgent;
     let ipad = ua.match(/(iPad).*OS\s([\d_]+)/);
     let isIphone = !ipad && ua.match(/(iPhone\sOS)\s([\d_]+)/);
     let isAndroid = ua.match(/(Android)\s+([\d.]+)/);
     isMobile = isIphone || isAndroid;
    if (isMobile) {
      document.getElementById("viewport").setAttribute("content", "user-scalable=yes, width=device-width, initial-scale=" + 0.4);
    }
  </script>
</html>


================================================
FILE: page/frontend/package.json
================================================
{
  "name": "frontend",
  "version": "1.0.0",
  "description": "A Vue.js project",
  "author": "liuxu",
  "private": true,
  "scripts": {
    "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
    "start": "npm run dev",
    "build": "node build/build.js",
    "postbuild": "rm -rf ../backend/dist && mv dist ../backend/"
  },
  "dependencies": {
    "axios": "^1.1.3",
    "element-ui": "^2.15.12",
    "modern-normalize": "^1.1.0",
    "nes.css": "^2.3.0",
    "vue": "^2.5.2",
    "vue-router": "^3.0.1"
  },
  "devDependencies": {
    "autoprefixer": "^7.1.2",
    "babel-core": "^6.22.1",
    "babel-helper-vue-jsx-merge-props": "^2.0.3",
    "babel-loader": "^7.1.1",
    "babel-plugin-component": "^1.1.1",
    "babel-plugin-syntax-jsx": "^6.18.0",
    "babel-plugin-transform-runtime": "^6.22.0",
    "babel-plugin-transform-vue-jsx": "^3.5.0",
    "babel-preset-env": "^1.3.2",
    "babel-preset-es2015": "^6.24.1",
    "babel-preset-stage-2": "^6.22.0",
    "chalk": "^2.0.1",
    "copy-webpack-plugin": "^4.0.1",
    "extract-text-webpack-plugin": "^3.0.0",
    "file-loader": "^1.1.4",
    "friendly-errors-webpack-plugin": "^1.6.1",
    "html-webpack-plugin": "^2.30.1",
    "node-notifier": "^5.1.2",
    "optimize-css-assets-webpack-plugin": "^3.2.0",
    "ora": "^1.2.0",
    "portfinder": "^1.0.13",
    "postcss-import": "^11.0.0",
    "postcss-loader": "^2.0.8",
    "postcss-url": "^7.2.1",
    "rimraf": "^2.6.0",
    "semver": "^5.3.0",
    "shelljs": "^0.7.6",
    "uglifyjs-webpack-plugin": "^1.1.1",
    "url-loader": "^0.5.8",
    "vue-loader": "^13.3.0",
    "vue-template-compiler": "^2.5.2",
    "webpack": "^3.6.0",
    "webpack-bundle-analyzer": "^2.9.0",
    "webpack-dev-server": "^2.9.1",
    "webpack-merge": "^4.1.0"
  },
  "engines": {
    "node": ">= 6.0.0",
    "npm": ">= 3.0.0"
  },
  "browserslist": [
    "> 1%",
    "last 2 versions",
    "not ie <= 8"
  ]
}


================================================
FILE: page/frontend/src/App.vue
================================================
<template>
  <div id="app">
    <router-view/>
  </div>
</template>

<script>
export default {
  name: 'App',
  watch: {
        $route: {
            immediate: true,
            handler(to, from) {
                document.title = to.meta.title || 'MoYu';
            }
        },
    }
}
</script>

<style>
@import url('https://fonts.googleapis.com/css?family=Press+Start+2P');
#app {
  background: #FFFFFA;
}
</style>


================================================
FILE: page/frontend/src/common/filters.js
================================================
/**
 * 转换成可读字节
 */
export function humanByte(size) {
    if (size < 1024) {
        return size + " Byte"
    } else if (size < 1024 * 1024) {
        return Math.trunc(size / 1024) + " KB"
    } else if (size < 1024 * 1024 * 1024) {
        return (size / 1024 / 1024).toFixed(2) + " MB"
    } else {
        return (size / 1024 / 1024 / 1024).toFixed(2) + " GB"
    }
}

/**
 * 将秒数转换成可读日时分秒
 * 例:3600秒->1小时
 * 例:183秒->3分3秒
 */
export function humanSec(sec) {
    if (sec < 60) {
        return Math.trunc(sec) + " seconds";
    } else if (sec < 3600) {
        var sec = sec % 60
        var min = Math.trunc(sec / 60);
        return min + " minutes " + sec + " seconds"
    } else if (sec < 86400) {
        var min = Math.trunc(sec % 3600 / 60);
        var hour = Math.trunc(sec / 3600);
        return hour + " hours " + min + " minutes"
    } else {
        var hour = Math.trunc(sec % 86400 / 3600);
        var day = Math.trunc(sec / 86400);
        return day + " days " + hour + " hours"
    }
}

/**
 * 转换为num位百分比
 */
export function humanPerc(float, num) {
    if (!float || !num) {
        return 0
    }
    return float.toFixed(num)
}

================================================
FILE: page/frontend/src/components/Cpu.vue
================================================
<template>
    <div>
        <el-row v-for="(percent, index) in cpu_percent" :key="index">
            <el-col>
                <progress class="nes-progress is-success" :value="percent" max="100"></progress>
            </el-col>
        </el-row>
    </div>
</template>

<script>
    export default {
        name: "cpu",
        props: ['cpu_percent'],
    }
</script>


================================================
FILE: page/frontend/src/components/Disk.vue
================================================
<template>
    <div>
        <el-row v-for="partition in partitions" :key="partition.device">
            <el-col>
                <span>{{ partition.device }}({{ partition.size | humanByte }}) | Used({{ usedDisk(partition.size, partition.used_percent) | humanByte }}) | Mounted({{ partition.mount_point }}):</span>
            </el-col>
            <el-col>
                <progress class="nes-progress is-primary" :value="partition.used_percent" max="100"></progress>
            </el-col>
        </el-row>
    </div>
</template>

<script>
    import {humanByte, humanPerc} from "@/common/filters"
    export default {
        name: "meminfo",
        props: ['partitions'],
        filters: {
            humanByte: function(size) {
                return humanByte(size)
            },
            humanPerc: function(float, num) {
                return humanPerc(float, num)
            },
        },
        methods: {
            usedDisk(diskSize, percent) {
                return diskSize * percent / 100;
            },
        }
    }
</script>

================================================
FILE: page/frontend/src/components/Host.vue
================================================
<template>
    <div>
        <el-row>
            <el-col :xs="7" :sm="7" :md="5" :lg="4" :xl="4"><span>Hostname:</span></el-col>
            <el-col :xs="17" :sm="17" :md="19" :lg="20" :xl="20"><span>{{ info.hostname }}</span></el-col>
        </el-row>
        <el-row>
            <el-col :xs="7" :sm="7" :md="5" :lg="4" :xl="4"><span>OS:</span></el-col>
            <el-col :xs="17" :sm="17" :md="19" :lg="20" :xl="20"><span>{{ info.os }}</span></el-col>
        </el-row>
        <el-row>
            <el-col :xs="7" :sm="7" :md="5" :lg="4" :xl="4"><span>CPU:</span></el-col>
            <el-col :xs="17" :sm="17" :md="19" :lg="20" :xl="20"><span>{{ info.cpu }}</span></el-col>
        </el-row>
        <el-row>
            <el-col :xs="7" :sm="7" :md="5" :lg="4" :xl="4"><span>AES-NI:</span></el-col>
            <el-col :xs="17" :sm="17" :md="19" :lg="20" :xl="20"><span>{{ info.aes_ni }}</span></el-col>
        </el-row>
        <el-row>
            <el-col :xs="7" :sm="7" :md="5" :lg="4" :xl="4"><span>VM-x/AMD-V:</span></el-col>
            <el-col :xs="17" :sm="17" :md="19" :lg="20" :xl="20"><span>{{ info.vm_x_amd_v }}</span></el-col>
        </el-row>
        <el-row>
            <el-col :xs="7" :sm="7" :md="5" :lg="4" :xl="4"><span>Virt:</span></el-col>
            <el-col :xs="17" :sm="17" :md="19" :lg="20" :xl="20"><span>{{ info.virt }}</span></el-col>
        </el-row>
        <el-row>
            <el-col :xs="7" :sm="7" :md="5" :lg="4" :xl="4"><span>Uptime:</span></el-col>
            <el-col :xs="17" :sm="17" :md="19" :lg="20" :xl="20"><span>{{ info.uptime | humanSec }}</span></el-col>
        </el-row>
        <el-row>
            <el-col :xs="7" :sm="7" :md="5" :lg="4" :xl="4"><span>Load:</span></el-col>
            <el-col :xs="6" :sm="6" :md="5" :lg="4" :xl="4">
                <div class="nes-badge is-splited">
                    <span class="is-dark">1min</span>
                    <span class="is-warning">{{ info.load_avg.load1 }}</span>
                </div>
            </el-col>
            <el-col :xs="6" :sm="6" :md="5" :lg="4" :xl="4">
                <div class="nes-badge is-splited">
                    <span class="is-dark">5min</span>
                    <span class="is-warning">{{ info.load_avg.load5 }}</span>
                </div>
            </el-col>
            <el-col :xs="5" :sm="5" :md="5" :lg="4" :xl="4">
                <div class="nes-badge is-splited">
                    <span class="is-dark">15min</span>
                    <span class="is-warning">{{ info.load_avg.load15 }}</span>
                </div>
            </el-col>
        </el-row>
        <el-row>
            <el-col :xs="7" :sm="7" :md="5" :lg="4" :xl="4"><span>BW(u|d):</span></el-col>
            <el-col :xs="9" :sm="10" :md="9" :lg="8" :xl="8" v-for="ifce in info.ifces" :key="ifce.name">
                <div class="nes-badge is-splited net-badge">
                    <span class="is-dark">{{ curNetSend(ifce.send_byte) | humanByte }}/s</span>
                    <span class="is-warning">{{ curNetRecv(ifce.recv_byte) | humanByte  }}/s</span>
                </div>
            </el-col>
        </el-row>
        <el-row>
            <el-col :xs="7" :sm="7" :md="5" :lg="4" :xl="4"><span>Traffic:</span></el-col>
            <el-col :xs="9" :sm="10" :md="9" :lg="8" :xl="8" v-for="ifce in info.ifces" :key="ifce.name">
                <div class="nes-badge is-splited net-badge">
                    <span class="is-dark">{{ ifce.send_byte | humanByte }}</span>
                    <span class="is-warning">{{ ifce.recv_byte | humanByte  }}</span>
                </div>
            </el-col>
        </el-row>
    </div>
</template>

<script>
    import {humanSec, humanByte} from "@/common/filters"
    export default {
        name: "host",
        props: ['info'],
        methods: {
            curNetSend: function(curTotalSend) {
                var lastTotalSend = sessionStorage.getItem("lastTotalSend")
                if (!lastTotalSend) {
                    lastTotalSend = curTotalSend
                }
                sessionStorage.setItem("lastTotalSend", curTotalSend)
                return curTotalSend - lastTotalSend
            },
            curNetRecv: function(curTotalRecv) {
                var lastTotalRecv = sessionStorage.getItem("lastTotalRecv")
                if (!lastTotalRecv) {
                    lastTotalRecv = curTotalRecv
                }
                sessionStorage.setItem("lastTotalRecv", curTotalRecv)
                return curTotalRecv - lastTotalRecv
            }
        },
        filters: {
            humanSec: function(sec) {
                return humanSec(sec)
            },
            humanByte: function(size) {
                return humanByte(size)
            }
        },
    }
</script>
<style scoped>
.el-row {
    margin-top: 5px;
}
.net-badge {
    width: 330px;
}
</style>


================================================
FILE: page/frontend/src/components/Mem.vue
================================================
<template>
    <div>
        <el-row>
            <el-col>
                <span>Memory({{ info.memory | humanByte }}) | Used({{ usedMem | humanByte }}):</span>
            </el-col>
            <el-col>
                <progress class="nes-progress is-error" :value="status.memory_percent" max="100"></progress>
            </el-col>
        </el-row>
        <el-row>
            <el-col>
                <span>Swap({{ info.swap | humanByte }}) | Used({{ usedSwap | humanByte }}):</span>
            </el-col>
            <el-col>
                <progress class="nes-progress is-error" :value="status.swap_percent" max="100"></progress>
            </el-col>
        </el-row>
    </div>
</template>

<script>
    import {humanByte, humanPerc} from "@/common/filters"
    export default {
        name: "mee",
        props: ['info', 'status'],
        filters: {
            humanByte: function(size) {
                return humanByte(size)
            },
            humanPerc: function(float, num) {
                return humanPerc(float, num)
            }
        },
        computed: {
            usedMem() {
                return this.info.memory * this.status.memory_percent / 100;
            },
            usedSwap() {
                return this.info.swap * this.status.swap_percent / 100;
            },
        }
    }
</script>

================================================
FILE: page/frontend/src/layouts/Main.vue
================================================
<template>
  <div class="container">
    <slot></slot>
  </div>
</template>

<script>
</script>

<style scoped>
.container {
    max-width: 1400px;
    margin: 0 auto;
    padding: 15px 30px;
}
</style>

================================================
FILE: page/frontend/src/main.js
================================================
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'

import "modern-normalize/modern-normalize.css"
import {
  Row,
  Col,
  Container,
  Header,
  Main,
  Footer,
} from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import axios from 'axios'
import "nes.css/css/nes.min.css"

Vue.config.productionTip = false
Vue.prototype.axios = axios

Vue.component(Row.name, Row)
Vue.component(Col.name, Col)
Vue.component(Container.name, Container)
Vue.component(Header.name, Header)
Vue.component(Main.name, Main)
Vue.component(Footer.name, Footer)

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  components: { App },
  template: '<App/>'
})


================================================
FILE: page/frontend/src/pages/SystemPage.vue
================================================
<template>
    <el-container class="content-container">
        <el-header height="80px">
            <div class="logo">
                <span>MoYu</span>
            </div>
            <div class="github-logo">
                <a href="https://github.com/liuquanhao/moyu" target="_blank" rel="noopener">
                    <i class="nes-icon github is-large"></i>
                </a>
            </div>
        </el-header>
        <el-main>
            <div class="nes-container with-title">
                <p class="title">Basic System Information</p>
                <host :info="host_data"/>
                <div class="nes-container with-title is-centered component-container">
                    <p class="title">CPU</p>
                    <cpu :cpu_percent="cpu_percent" />
                </div>
                <div class="nes-container with-title is-centered component-container">
                    <p class="title">Memory</p>
                    <mem :info="memory_info" :status="memory_status" />
                </div>
                <div class="nes-container with-title is-centered component-container">
                    <p class="title">Disk</p>
                    <disk :partitions="partitions" />
                </div>
            </div>
        </el-main>
        <el-footer>
            <p class="powered-by">Powered By: liuxu</p>
        </el-footer>
    </el-container>
</template>
  
<script>
    import MainLayout from '../layouts/Main.vue'
    import Host from '../components/Host.vue'
    import Cpu from '../components/Cpu.vue'
    import Mem from '../components/Mem.vue'
    import Disk from '../components/Disk.vue'
    export default {
        components: {
            MainLayout,
            Host,
            Cpu,
            Mem,
            Disk,
        },
        data: function() {
            return {
                ws: null,
                host_info: {},
                cpu_info: {
                    "count": 0,
                    "cores": [],
                },
                cpu_percent: {},
                load_avg: {},
                memory_info: {},
                memory_status: {},
                ifces: {},
                partitions: {},
                uptime: {},
                timestamp: 0,
            }
        },
        computed: {
            host_data: function() {
                let hostname = this.host_info.hostname
                let os = this.host_info.distribution + " (" + this.host_info.kernel + " " + this.host_info.arch + ")"
                let cpu = 'unknown'
                let aes_ni = false
                let vm_x_amd_v = false
                if (this.cpu_info.cores.length > 0) {
                    cpu = this.cpu_info.cores[0].model + " @ " + this.cpu_info.cores[0].mhz
                    aes_ni = this.cpu_info.cores[0].flags.includes("aes")
                    vm_x_amd_v = this.cpu_info.cores[0].flags.includes("vmx") || this.cpu_info.cores[0].flags.includes("svm")
                }
                let virt = this.host_info.virtual_platform ? this.host_info.virtual_platform : 'unknown'
                let uptime = this.host_info.uptime
                let load_avg = this.load_avg
                let ifces = this.ifces
                return {
                    hostname,
                    os,
                    cpu,
                    aes_ni,
                    vm_x_amd_v,
                    virt,
                    uptime,
                    load_avg,
                    ifces,
                }
            },
        },
        created() {
            this.initData()
            this.initWs()
        },
        destroyed() {
            this.ws.close()
        },
        methods: {
            initData() {

                this.axios.get("./api/sys_info").then(
                    res => {
                        this.host_info = res.data.host_info
                        this.cpu_info = res.data.cpu_info
                        this.load_avg = res.data.system_status.load_avg
                        this.memory_info = res.data.memory_info
                        this.ifces = res.data.network_info.ifces
                        this.partitions = res.data.disk_info.partitions
                        this.cpu_percent = res.data.system_status.cpu_percent
                        this.memory_status = res.data.system_status.memory_status
                        this.timestamp = res.data.timestamp
                    }
                ).catch(res => {
                    console.log(res)
                })
            },
            initWs() {
                let wsProtocol = window.location.protocol == "https:" ? "wss://" : "ws://";
                let wsPort = window.location.port == "" ? "" : ":" + window.location.port;
                this.ws = new WebSocket(wsProtocol + window.location.hostname + wsPort + window.location.pathname + "ws/sys_status")
                this.ws.onopen = this.wsOnOpen
                this.ws.onerror = this.wsOnError
                this.ws.onmessage = this.wsOnMessage
                this.ws.onclose = this.wsOnClose
            },
            wsOnOpen() {
                console.log("ws连接成功")
            },
            wsOnError() {
                console.log("ws连接错误")
                this.initWs()
            },
            wsOnMessage(e) {
                var systemStatus = JSON.parse(e.data)
                this.cpu_percent = systemStatus.cpu_percent
                this.load_avg = systemStatus.load_avg
                this.memory_status = systemStatus.memory_status
                this.ifces = systemStatus.network_info.ifces
                this.partitions = systemStatus.disk_info.partitions
                this.uptime = systemStatus.uptime
            },
            wsOnClose(e) {
                console.log("ws断开连接")
            }
        }
    }
</script>

<style scoped>
.logo {
    margin-right: auto;
    margin-left: 10px;
    font-size: 50px;
    height: 10px;
}
.github-logo {
    height: 10px;
    margin-right: 10px;
}
.content-container {
    min-width: 780px;
    max-width: 1260px;
    margin: 10px auto;
    justify-content: center;
}
.el-header {
    left: 0;
    right: 0;
    display: flex;
    flex-direction: row;
    border-bottom: 4px solid #D3D3D3; 
}
.nes-octocat {
    height: 30px;
}
.component-container {
    margin-top: 20px;
}
.el-footer {
    margin-top: 10px;
    margin-left: auto;
}
.powered-by {
    margin-right: 10px;
}
</style>

================================================
FILE: page/frontend/src/router/index.js
================================================
import Vue from 'vue'
import Router from 'vue-router'
import SystemPage from '@/pages/SystemPage'

Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',
      name: 'SystemPage',
      component: SystemPage
    }
  ]
})


================================================
FILE: page/frontend/static/.gitkeep
================================================
Download .txt
gitextract_ebjt9uy3/

├── .github/
│   └── workflows/
│       └── release.yml
├── .gitignore
├── Makefile
├── ManagerDockerfile
├── PageDockerfile
├── README.md
├── manager/
│   ├── backend/
│   │   ├── .gitignore
│   │   ├── controller/
│   │   │   ├── page_data_controller/
│   │   │   │   └── page_data_controller.go
│   │   │   ├── page_url_controller/
│   │   │   │   └── page_url_controller.go
│   │   │   └── user_controller/
│   │   │       └── user_controller.go
│   │   ├── database/
│   │   │   └── init.sql
│   │   ├── go.mod
│   │   ├── go.sum
│   │   ├── main.go
│   │   ├── middleware/
│   │   │   ├── logged.go
│   │   │   └── ws_options.go
│   │   └── model/
│   │       ├── db.go
│   │       ├── remote_url_model/
│   │       │   └── remote_url_model.go
│   │       ├── session.go
│   │       └── user_model/
│   │           └── user_model.go
│   └── frontend/
│       ├── .babelrc
│       ├── .editorconfig
│       ├── .gitignore
│       ├── .postcssrc.js
│       ├── README.md
│       ├── build/
│       │   ├── build.js
│       │   ├── check-versions.js
│       │   ├── utils.js
│       │   ├── vue-loader.conf.js
│       │   ├── webpack.base.conf.js
│       │   ├── webpack.dev.conf.js
│       │   └── webpack.prod.conf.js
│       ├── config/
│       │   ├── dev.env.js
│       │   ├── index.js
│       │   └── prod.env.js
│       ├── index.html
│       ├── package.json
│       ├── src/
│       │   ├── App.vue
│       │   ├── components/
│       │   │   ├── Login.vue
│       │   │   ├── MoyuFooter.vue
│       │   │   └── MoyuNav.vue
│       │   ├── layouts/
│       │   │   └── PageLayout.vue
│       │   ├── main.js
│       │   ├── pages/
│       │   │   ├── AddUrlPage.vue
│       │   │   ├── LoginPage.vue
│       │   │   └── MainPage.vue
│       │   └── router/
│       │       └── index.js
│       └── static/
│           └── .gitkeep
└── page/
    ├── backend/
    │   ├── .gitignore
    │   ├── controller/
    │   │   └── system.go
    │   ├── go.mod
    │   ├── go.sum
    │   ├── main.go
    │   ├── middleware/
    │   │   └── ws_options.go
    │   └── service/
    │       ├── cpu.go
    │       ├── disk.go
    │       ├── host.go
    │       ├── memory.go
    │       ├── network.go
    │       ├── page_data.go
    │       └── system.go
    └── frontend/
        ├── .babelrc
        ├── .editorconfig
        ├── .gitignore
        ├── .postcssrc.js
        ├── README.md
        ├── build/
        │   ├── build.js
        │   ├── check-versions.js
        │   ├── utils.js
        │   ├── vue-loader.conf.js
        │   ├── webpack.base.conf.js
        │   ├── webpack.dev.conf.js
        │   └── webpack.prod.conf.js
        ├── config/
        │   ├── dev.env.js
        │   ├── index.js
        │   └── prod.env.js
        ├── index.html
        ├── package.json
        ├── src/
        │   ├── App.vue
        │   ├── common/
        │   │   └── filters.js
        │   ├── components/
        │   │   ├── Cpu.vue
        │   │   ├── Disk.vue
        │   │   ├── Host.vue
        │   │   └── Mem.vue
        │   ├── layouts/
        │   │   └── Main.vue
        │   ├── main.js
        │   ├── pages/
        │   │   └── SystemPage.vue
        │   └── router/
        │       └── index.js
        └── static/
            └── .gitkeep
Download .txt
SYMBOL INDEX (69 symbols across 30 files)

FILE: manager/backend/controller/page_data_controller/page_data_controller.go
  type PageData (line 14) | type PageData struct
  type PageInfo (line 24) | type PageInfo struct
  function getPageData (line 29) | func getPageData(myClient *http.Client, url string, target interface{}) ...
  function loopPullPageData (line 41) | func loopPullPageData(pageId uint32, url string, ch chan *PageInfo) {
  function PushPageData (line 63) | func PushPageData(c *websocket.Conn) {

FILE: manager/backend/controller/page_url_controller/page_url_controller.go
  function Add (line 13) | func Add(c *fiber.Ctx) error {
  function Delete (line 26) | func Delete(c *fiber.Ctx) error {
  function List (line 38) | func List(c *fiber.Ctx) error {

FILE: manager/backend/controller/user_controller/user_controller.go
  function Login (line 15) | func Login(c *fiber.Ctx) error {
  function Account (line 47) | func Account(c *fiber.Ctx) error {
  function Logout (line 62) | func Logout(c *fiber.Ctx) error {

FILE: manager/backend/database/init.sql
  type sessions (line 1) | CREATE TABLE IF NOT EXISTS sessions (
  type users (line 7) | CREATE TABLE IF NOT EXISTS users (
  type page_urls (line 12) | CREATE TABLE IF NOT EXISTS page_urls (
  type user_uiq (line 18) | CREATE UNIQUE INDEX user_uiq on users (user)

FILE: manager/backend/main.go
  function getCurrentPwd (line 28) | func getCurrentPwd() string {
  function initDatabase (line 32) | func initDatabase() {
  function initSession (line 40) | func initSession() {
  function setupRoutes (line 56) | func setupRoutes(app *fiber.App) {
  function main (line 90) | func main() {

FILE: manager/backend/middleware/logged.go
  function Logged (line 8) | func Logged(c *fiber.Ctx) error {

FILE: manager/backend/middleware/ws_options.go
  function UpgradeOptions (line 8) | func UpgradeOptions(c *fiber.Ctx) error {

FILE: manager/backend/model/remote_url_model/remote_url_model.go
  type PageUrl (line 3) | type PageUrl struct

FILE: manager/backend/model/user_model/user_model.go
  type User (line 3) | type User struct

FILE: manager/frontend/build/check-versions.js
  function exec (line 7) | function exec (cmd) {

FILE: manager/frontend/build/utils.js
  function generateLoaders (line 33) | function generateLoaders (loader, loaderOptions) {

FILE: manager/frontend/build/webpack.base.conf.js
  function resolve (line 7) | function resolve (dir) {

FILE: manager/frontend/build/webpack.dev.conf.js
  constant HOST (line 13) | const HOST = process.env.HOST
  constant PORT (line 14) | const PORT = process.env.PORT && Number(process.env.PORT)

FILE: manager/frontend/build/webpack.prod.conf.js
  method minChunks (line 84) | minChunks (module) {

FILE: page/backend/controller/system.go
  function GetSysInfo (line 11) | func GetSysInfo(c *fiber.Ctx) error {
  function PushSysStatus (line 15) | func PushSysStatus(c *websocket.Conn) {
  function GetPageData (line 22) | func GetPageData(c *fiber.Ctx) error {

FILE: page/backend/main.go
  function main (line 22) | func main() {

FILE: page/backend/middleware/ws_options.go
  function UpgradeOptions (line 8) | func UpgradeOptions(c *fiber.Ctx) error {

FILE: page/backend/service/cpu.go
  type Core (line 5) | type Core struct
  type CPUInfo (line 12) | type CPUInfo struct
    method AppendCore (line 17) | func (cpuInfo *CPUInfo) AppendCore(core *Core) {
  function GetCPUInfo (line 21) | func GetCPUInfo() *CPUInfo {

FILE: page/backend/service/disk.go
  type Partition (line 9) | type Partition struct
  type DiskInfo (line 15) | type DiskInfo struct
    method AppendPartition (line 19) | func (diskInfo *DiskInfo) AppendPartition(partition *Partition) {
  function GetDiskInfo (line 23) | func GetDiskInfo() *DiskInfo {

FILE: page/backend/service/host.go
  type HostInfo (line 7) | type HostInfo struct
  function GetHostInfo (line 16) | func GetHostInfo() *HostInfo {
  function GetUptime (line 28) | func GetUptime() uint64 {

FILE: page/backend/service/memory.go
  type MemoryInfo (line 7) | type MemoryInfo struct
  type MemoryStatus (line 12) | type MemoryStatus struct
  function GetMemoryInfo (line 17) | func GetMemoryInfo() *MemoryInfo {
  function GetMemoryStatus (line 26) | func GetMemoryStatus() *MemoryStatus {

FILE: page/backend/service/network.go
  type Ifce (line 9) | type Ifce struct
  type NetworkInfo (line 15) | type NetworkInfo struct
    method AppendIfce (line 19) | func (networkInfo *NetworkInfo) AppendIfce(ifce *Ifce) {
  function GetNetworkInfo (line 23) | func GetNetworkInfo() *NetworkInfo {

FILE: page/backend/service/page_data.go
  type PageData (line 13) | type PageData struct
  function GetPageData (line 23) | func GetPageData() *PageData {

FILE: page/backend/service/system.go
  type SystemInfo (line 10) | type SystemInfo struct
  type SystemStatus (line 19) | type SystemStatus struct
  function GetSystemInfo (line 29) | func GetSystemInfo() *SystemInfo {
  function GetSystemStatus (line 40) | func GetSystemStatus() *SystemStatus {

FILE: page/frontend/build/check-versions.js
  function exec (line 7) | function exec (cmd) {

FILE: page/frontend/build/utils.js
  function generateLoaders (line 33) | function generateLoaders (loader, loaderOptions) {

FILE: page/frontend/build/webpack.base.conf.js
  function resolve (line 7) | function resolve (dir) {

FILE: page/frontend/build/webpack.dev.conf.js
  constant HOST (line 13) | const HOST = process.env.HOST
  constant PORT (line 14) | const PORT = process.env.PORT && Number(process.env.PORT)

FILE: page/frontend/build/webpack.prod.conf.js
  method minChunks (line 84) | minChunks (module) {

FILE: page/frontend/src/common/filters.js
  function humanByte (line 4) | function humanByte(size) {
  function humanSec (line 21) | function humanSec(sec) {
  function humanPerc (line 42) | function humanPerc(float, num) {
Condensed preview — 89 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (142K chars).
[
  {
    "path": ".github/workflows/release.yml",
    "chars": 1997,
    "preview": "name: 编译并发布项目\non:\n  push:\n    tags:\n      - 'v*'\njobs:\n  release:\n    name: \"编译发布release程序\"\n    runs-on: ubuntu-latest\n "
  },
  {
    "path": ".gitignore",
    "chars": 7,
    "preview": "target/"
  },
  {
    "path": "Makefile",
    "chars": 716,
    "preview": "all: manager page\n\tmkdir -p target/bin && mkdir -p target/db && mv manager/backend/moyu-manager target/bin/ && mv page/b"
  },
  {
    "path": "ManagerDockerfile",
    "chars": 409,
    "preview": "FROM alpine:3.17.1 as builder\nCOPY . .\nRUN apk add go nodejs npm\nRUN cd manager/frontend && npm install && npm run build"
  },
  {
    "path": "PageDockerfile",
    "chars": 301,
    "preview": "FROM alpine:3.17.1 as builder\nCOPY . .\nRUN apk add go nodejs npm\nRUN cd page/frontend && npm install && npm run build &&"
  },
  {
    "path": "README.md",
    "chars": 4118,
    "preview": "# 墨鱼探针\n\n## 简介\n\n墨鱼探针为主从模式,主为`墨鱼manager`,从为`墨鱼page`。`墨鱼manager`可获取展示多个墨鱼page的简要状态信息,登录后可点击title进入墨鱼page页面。`墨鱼page`独立部署,可独立"
  },
  {
    "path": "manager/backend/.gitignore",
    "chars": 18,
    "preview": "dist/\nmoyu-manager"
  },
  {
    "path": "manager/backend/controller/page_data_controller/page_data_controller.go",
    "chars": 1787,
    "preview": "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"
  },
  {
    "path": "manager/backend/controller/page_url_controller/page_url_controller.go",
    "chars": 1200,
    "preview": "package page_url_controller\n\nimport (\n\t\"github.com/gofiber/fiber/v2\"\n\t\"github.com/liuquanhao/moyu/model\"\n\t\"github.com/li"
  },
  {
    "path": "manager/backend/controller/user_controller/user_controller.go",
    "chars": 1773,
    "preview": "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/fib"
  },
  {
    "path": "manager/backend/database/init.sql",
    "chars": 569,
    "preview": "CREATE TABLE IF NOT EXISTS sessions (\n    k  VARCHAR(64) PRIMARY KEY NOT NULL DEFAULT '',\n    v  BLOB NOT NULL,\n    e  B"
  },
  {
    "path": "manager/backend/go.mod",
    "chars": 1519,
    "preview": "module github.com/liuquanhao/moyu\n\ngo 1.19\n\nrequire (\n\tgithub.com/go-playground/validator/v10 v10.11.2\n\tgithub.com/gofib"
  },
  {
    "path": "manager/backend/go.sum",
    "chars": 10257,
    "preview": "github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=\ngithub.com/andybalholm/brotli v1.0."
  },
  {
    "path": "manager/backend/main.go",
    "chars": 2439,
    "preview": "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"
  },
  {
    "path": "manager/backend/middleware/logged.go",
    "chars": 257,
    "preview": "package middleware\n\nimport (\n\t\"github.com/gofiber/fiber/v2\"\n\t\"github.com/liuquanhao/moyu/model\"\n)\n\nfunc Logged(c *fiber."
  },
  {
    "path": "manager/backend/middleware/ws_options.go",
    "chars": 234,
    "preview": "package middleware\n\nimport (\n\t\"github.com/gofiber/fiber/v2\"\n\t\"github.com/gofiber/websocket/v2\"\n)\n\nfunc UpgradeOptions(c "
  },
  {
    "path": "manager/backend/model/db.go",
    "chars": 63,
    "preview": "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",
    "chars": 198,
    "preview": "package remote_url_model\n\ntype PageUrl struct {\n\tId      uint32 `json:\"id\"`\n\tTitle   string `json:\"title\" validate:\"requ"
  },
  {
    "path": "manager/backend/model/session.go",
    "chars": 109,
    "preview": "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",
    "chars": 178,
    "preview": "package user_model\n\ntype User struct {\n\tId    uint32 `json:\"id\"`\n\tUser  string `json:\"user\" validate:\"required,max=16\"`\n"
  },
  {
    "path": "manager/frontend/.babelrc",
    "chars": 230,
    "preview": "{\n  \"presets\": [\n    [\"env\", {\n      \"modules\": false,\n      \"targets\": {\n        \"browsers\": [\"> 1%\", \"last 2 versions\""
  },
  {
    "path": "manager/frontend/.editorconfig",
    "chars": 147,
    "preview": "root = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\nend_of_line = lf\ninsert_final_newline = true\ntrim_"
  },
  {
    "path": "manager/frontend/.gitignore",
    "chars": 154,
    "preview": ".DS_Store\nnode_modules/\n/dist/\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# Editor directories and files\n.idea\n.vsc"
  },
  {
    "path": "manager/frontend/.postcssrc.js",
    "chars": 246,
    "preview": "// https://github.com/michael-ciniawsky/postcss-load-config\n\nmodule.exports = {\n  \"plugins\": {\n    \"postcss-import\": {},"
  },
  {
    "path": "manager/frontend/README.md",
    "chars": 461,
    "preview": "# frontend\n\n> moyu manager\n\n## Build Setup\n\n``` bash\n# install dependencies\nnpm install\n\n# serve with hot reload at loca"
  },
  {
    "path": "manager/frontend/build/build.js",
    "chars": 1198,
    "preview": "'use strict'\nrequire('./check-versions')()\n\nprocess.env.NODE_ENV = 'production'\n\nconst ora = require('ora')\nconst rm = r"
  },
  {
    "path": "manager/frontend/build/check-versions.js",
    "chars": 1290,
    "preview": "'use strict'\nconst chalk = require('chalk')\nconst semver = require('semver')\nconst packageConfig = require('../package.j"
  },
  {
    "path": "manager/frontend/build/utils.js",
    "chars": 2587,
    "preview": "'use strict'\nconst path = require('path')\nconst config = require('../config')\nconst ExtractTextPlugin = require('extract"
  },
  {
    "path": "manager/frontend/build/vue-loader.conf.js",
    "chars": 553,
    "preview": "'use strict'\nconst utils = require('./utils')\nconst config = require('../config')\nconst isProduction = process.env.NODE_"
  },
  {
    "path": "manager/frontend/build/webpack.base.conf.js",
    "chars": 2047,
    "preview": "'use strict'\nconst path = require('path')\nconst utils = require('./utils')\nconst config = require('../config')\nconst vue"
  },
  {
    "path": "manager/frontend/build/webpack.dev.conf.js",
    "chars": 3004,
    "preview": "'use strict'\nconst utils = require('./utils')\nconst webpack = require('webpack')\nconst config = require('../config')\ncon"
  },
  {
    "path": "manager/frontend/build/webpack.prod.conf.js",
    "chars": 5055,
    "preview": "'use strict'\nconst path = require('path')\nconst utils = require('./utils')\nconst webpack = require('webpack')\nconst conf"
  },
  {
    "path": "manager/frontend/config/dev.env.js",
    "chars": 156,
    "preview": "'use strict'\nconst merge = require('webpack-merge')\nconst prodEnv = require('./prod.env')\n\nmodule.exports = merge(prodEn"
  },
  {
    "path": "manager/frontend/config/index.js",
    "chars": 1980,
    "preview": "'use strict'\n// Template version: 1.3.1\n// see http://vuejs-templates.github.io/webpack for documentation.\n\nconst path ="
  },
  {
    "path": "manager/frontend/config/prod.env.js",
    "chars": 61,
    "preview": "'use strict'\nmodule.exports = {\n  NODE_ENV: '\"production\"'\n}\n"
  },
  {
    "path": "manager/frontend/index.html",
    "chars": 274,
    "preview": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width,initial"
  },
  {
    "path": "manager/frontend/package.json",
    "chars": 1959,
    "preview": "{\n  \"name\": \"frontend\",\n  \"version\": \"1.0.0\",\n  \"description\": \"moyu manager\",\n  \"author\": \"liuxu <i@liuxu.me>\",\n  \"priv"
  },
  {
    "path": "manager/frontend/src/App.vue",
    "chars": 242,
    "preview": "<template>\n  <div id=\"app\">\n    <router-view/>\n  </div>\n</template>\n\n<script>\nexport default {\n  name: 'App'\n}\n</script>"
  },
  {
    "path": "manager/frontend/src/components/Login.vue",
    "chars": 718,
    "preview": "<template>\n    <div class=\"nes-container with-title is-centered\">\n        <p class=\"title\">Login</p>\n        <div class="
  },
  {
    "path": "manager/frontend/src/components/MoyuFooter.vue",
    "chars": 239,
    "preview": "<template>\n    <div class=\"moyu-footer\">\n        <p class=\"powered-by\">Powered By: liuxu</p>\n    </div>\n</template>\n\n<st"
  },
  {
    "path": "manager/frontend/src/components/MoyuNav.vue",
    "chars": 2496,
    "preview": "<template>\n    <div class=\"moyu-nav\">\n        <div class=\"nav-left\">\n            <div class=\"logo\">\n                <a h"
  },
  {
    "path": "manager/frontend/src/layouts/PageLayout.vue",
    "chars": 768,
    "preview": "<template>\n    <el-container class=\"container\">\n        <el-header>\n            <moyu-nav></moyu-nav>\n        </el-heade"
  },
  {
    "path": "manager/frontend/src/main.js",
    "chars": 986,
    "preview": "// The Vue build version to load with the `import` command\n// (runtime-only or standalone) has been set in webpack.base."
  },
  {
    "path": "manager/frontend/src/pages/AddUrlPage.vue",
    "chars": 3414,
    "preview": "<template>\n    <page-layout>\n        <div class=\"nes-container with-title\">\n            <p class=\"title\">Add Page Url</p"
  },
  {
    "path": "manager/frontend/src/pages/LoginPage.vue",
    "chars": 2774,
    "preview": "<template>\n    <page-layout>\n        <div class=\"nes-container with-title is-centered\">\n            <p class=\"title\">Log"
  },
  {
    "path": "manager/frontend/src/pages/MainPage.vue",
    "chars": 8639,
    "preview": "<template>\n    <page-layout>\n        <div class=\"pages\">\n            <el-row type=\"flex\">\n                <el-col :span="
  },
  {
    "path": "manager/frontend/src/router/index.js",
    "chars": 502,
    "preview": "import Vue from 'vue'\nimport Router from 'vue-router'\nimport LoginPage from '@/pages/LoginPage'\nimport MainPage from '@/"
  },
  {
    "path": "manager/frontend/static/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "page/backend/.gitignore",
    "chars": 15,
    "preview": "dist/\nmoyu-page"
  },
  {
    "path": "page/backend/controller/system.go",
    "chars": 429,
    "preview": "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/liu"
  },
  {
    "path": "page/backend/go.mod",
    "chars": 1431,
    "preview": "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"
  },
  {
    "path": "page/backend/go.sum",
    "chars": 9316,
    "preview": "github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=\ngithub.com/andybalholm/brotli v1.0."
  },
  {
    "path": "page/backend/main.go",
    "chars": 1122,
    "preview": "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/fi"
  },
  {
    "path": "page/backend/middleware/ws_options.go",
    "chars": 234,
    "preview": "package middleware\n\nimport (\n\t\"github.com/gofiber/fiber/v2\"\n\t\"github.com/gofiber/websocket/v2\"\n)\n\nfunc UpgradeOptions(c "
  },
  {
    "path": "page/backend/service/cpu.go",
    "chars": 820,
    "preview": "package service\n\nimport \"github.com/shirou/gopsutil/v3/cpu\"\n\ntype Core struct {\n\tCPU       int32    `json:\"cpu\"`\n\tCoreID"
  },
  {
    "path": "page/backend/service/disk.go",
    "chars": 1081,
    "preview": "package service\n\nimport (\n\t\"strings\"\n\n\t\"github.com/shirou/gopsutil/v3/disk\"\n)\n\ntype Partition struct {\n\tDevice      stri"
  },
  {
    "path": "page/backend/service/host.go",
    "chars": 791,
    "preview": "package service\n\nimport (\n\t\"github.com/shirou/gopsutil/v3/host\"\n)\n\ntype HostInfo struct {\n\tHostname        string `json:"
  },
  {
    "path": "page/backend/service/memory.go",
    "chars": 667,
    "preview": "package service\n\nimport (\n\t\"github.com/shirou/gopsutil/v3/mem\"\n)\n\ntype MemoryInfo struct {\n\tMemory uint64 `json:\"memory\""
  },
  {
    "path": "page/backend/service/network.go",
    "chars": 770,
    "preview": "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:\""
  },
  {
    "path": "page/backend/service/page_data.go",
    "chars": 1063,
    "preview": "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.c"
  },
  {
    "path": "page/backend/service/system.go",
    "chars": 1427,
    "preview": "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 Sy"
  },
  {
    "path": "page/frontend/.babelrc",
    "chars": 197,
    "preview": "{\n  \"presets\": [[\"es2015\", { \"modules\": false }]],\n  \"plugins\": [\n    [\n      \"component\",\n      {\n        \"libraryName\""
  },
  {
    "path": "page/frontend/.editorconfig",
    "chars": 147,
    "preview": "root = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\nend_of_line = lf\ninsert_final_newline = true\ntrim_"
  },
  {
    "path": "page/frontend/.gitignore",
    "chars": 154,
    "preview": ".DS_Store\nnode_modules/\n/dist/\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# Editor directories and files\n.idea\n.vsc"
  },
  {
    "path": "page/frontend/.postcssrc.js",
    "chars": 246,
    "preview": "// https://github.com/michael-ciniawsky/postcss-load-config\n\nmodule.exports = {\n  \"plugins\": {\n    \"postcss-import\": {},"
  },
  {
    "path": "page/frontend/README.md",
    "chars": 465,
    "preview": "# 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 "
  },
  {
    "path": "page/frontend/build/build.js",
    "chars": 1198,
    "preview": "'use strict'\nrequire('./check-versions')()\n\nprocess.env.NODE_ENV = 'production'\n\nconst ora = require('ora')\nconst rm = r"
  },
  {
    "path": "page/frontend/build/check-versions.js",
    "chars": 1290,
    "preview": "'use strict'\nconst chalk = require('chalk')\nconst semver = require('semver')\nconst packageConfig = require('../package.j"
  },
  {
    "path": "page/frontend/build/utils.js",
    "chars": 2587,
    "preview": "'use strict'\nconst path = require('path')\nconst config = require('../config')\nconst ExtractTextPlugin = require('extract"
  },
  {
    "path": "page/frontend/build/vue-loader.conf.js",
    "chars": 553,
    "preview": "'use strict'\nconst utils = require('./utils')\nconst config = require('../config')\nconst isProduction = process.env.NODE_"
  },
  {
    "path": "page/frontend/build/webpack.base.conf.js",
    "chars": 2047,
    "preview": "'use strict'\nconst path = require('path')\nconst utils = require('./utils')\nconst config = require('../config')\nconst vue"
  },
  {
    "path": "page/frontend/build/webpack.dev.conf.js",
    "chars": 3004,
    "preview": "'use strict'\nconst utils = require('./utils')\nconst webpack = require('webpack')\nconst config = require('../config')\ncon"
  },
  {
    "path": "page/frontend/build/webpack.prod.conf.js",
    "chars": 5055,
    "preview": "'use strict'\nconst path = require('path')\nconst utils = require('./utils')\nconst webpack = require('webpack')\nconst conf"
  },
  {
    "path": "page/frontend/config/dev.env.js",
    "chars": 157,
    "preview": "'use strict'\nconst merge = require('webpack-merge')\nconst prodEnv = require('./prod.env')\n\nmodule.exports = merge(prodEn"
  },
  {
    "path": "page/frontend/config/index.js",
    "chars": 1980,
    "preview": "'use strict'\n// Template version: 1.3.1\n// see http://vuejs-templates.github.io/webpack for documentation.\n\nconst path ="
  },
  {
    "path": "page/frontend/config/prod.env.js",
    "chars": 61,
    "preview": "'use strict'\nmodule.exports = {\n  NODE_ENV: '\"production\"'\n}\n"
  },
  {
    "path": "page/frontend/index.html",
    "chars": 727,
    "preview": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" id=\"viewport\" content=\"width=device"
  },
  {
    "path": "page/frontend/package.json",
    "chars": 1936,
    "preview": "{\n  \"name\": \"frontend\",\n  \"version\": \"1.0.0\",\n  \"description\": \"A Vue.js project\",\n  \"author\": \"liuxu\",\n  \"private\": tru"
  },
  {
    "path": "page/frontend/src/App.vue",
    "chars": 422,
    "preview": "<template>\n  <div id=\"app\">\n    <router-view/>\n  </div>\n</template>\n\n<script>\nexport default {\n  name: 'App',\n  watch: {"
  },
  {
    "path": "page/frontend/src/common/filters.js",
    "chars": 1151,
    "preview": "/**\n * 转换成可读字节\n */\nexport function humanByte(size) {\n    if (size < 1024) {\n        return size + \" Byte\"\n    } else if "
  },
  {
    "path": "page/frontend/src/components/Cpu.vue",
    "chars": 372,
    "preview": "<template>\n    <div>\n        <el-row v-for=\"(percent, index) in cpu_percent\" :key=\"index\">\n            <el-col>\n        "
  },
  {
    "path": "page/frontend/src/components/Disk.vue",
    "chars": 1059,
    "preview": "<template>\n    <div>\n        <el-row v-for=\"partition in partitions\" :key=\"partition.device\">\n            <el-col>\n     "
  },
  {
    "path": "page/frontend/src/components/Host.vue",
    "chars": 4908,
    "preview": "<template>\n    <div>\n        <el-row>\n            <el-col :xs=\"7\" :sm=\"7\" :md=\"5\" :lg=\"4\" :xl=\"4\"><span>Hostname:</span>"
  },
  {
    "path": "page/frontend/src/components/Mem.vue",
    "chars": 1349,
    "preview": "<template>\n    <div>\n        <el-row>\n            <el-col>\n                <span>Memory({{ info.memory | humanByte }}) |"
  },
  {
    "path": "page/frontend/src/layouts/Main.vue",
    "chars": 202,
    "preview": "<template>\n  <div class=\"container\">\n    <slot></slot>\n  </div>\n</template>\n\n<script>\n</script>\n\n<style scoped>\n.contain"
  },
  {
    "path": "page/frontend/src/main.js",
    "chars": 826,
    "preview": "// The Vue build version to load with the `import` command\n// (runtime-only or standalone) has been set in webpack.base."
  },
  {
    "path": "page/frontend/src/pages/SystemPage.vue",
    "chars": 6475,
    "preview": "<template>\n    <el-container class=\"content-container\">\n        <el-header height=\"80px\">\n            <div class=\"logo\">"
  },
  {
    "path": "page/frontend/src/router/index.js",
    "chars": 246,
    "preview": "import Vue from 'vue'\nimport Router from 'vue-router'\nimport SystemPage from '@/pages/SystemPage'\n\nVue.use(Router)\n\nexpo"
  },
  {
    "path": "page/frontend/static/.gitkeep",
    "chars": 0,
    "preview": ""
  }
]

About this extraction

This page contains the full source code of the liuquanhao/moyu GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 89 files (125.3 KB), approximately 41.9k tokens, and a symbol index with 69 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!