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

### 墨鱼manager
未登录主页:

已登录主页:

登录页:

添加墨鱼page页面:

## 直接使用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
================================================
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
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.