[feat] support docker

1. embed web files
2. support docker
main
Johnson C 2 years ago
parent c454c6963b
commit 9cc7f84a21

@ -0,0 +1,2 @@
docs
frontend

5
.gitignore vendored

@ -14,5 +14,6 @@
# Swagger
docs/
# frontend dist
web/
# config files
config.yaml
config.toml

@ -1,23 +1,17 @@
FROM golang:1.17 as api-builder
FROM golang:1.17 as builder
WORKDIR /usr/local/bin
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o dashboard .
FROM node:17 as web-builder
WORKDIR /usr/local/bin
RUN cd frontend && npm install && ng build --output-path web
RUN git clone https://github.com/xpunch/go-micro-dashboard.git /usr/local/micro \
&& cd /usr/local/micro \
&& go install github.com/swaggo/swag/cmd/swag@latest \
&& swag init \
&& CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o dashboard .
FROM alpine:latest
WORKDIR /usr/local/bin
COPY --from=api-builder /usr/local/bin/dashboard .
COPY --from=web-builder /usr/local/bin/web .
COPY --from=builder /usr/local/micro/dashboard .
EXPOSE 80
CMD [ "/usr/local/bin/dashboard" ]
ENTRYPOINT [ "/usr/local/bin/dashboard" ]

@ -5,13 +5,19 @@ Go micro dashboard is designed to make it as easy as possible for users to work
## Features
- [ ] Logo
- [x] Dashboard
- [x] Services list
- [x] Web UI
- [x] Service discovery
- [ ] Health check
- [ ] Configure service
- [ ] Endpoint request
- [x] Endpoint request
- [ ] Broker messages
## Installation
```
go install github.com/xpunch/go-micro-dashboard@latest
```
## Development
### Server
@ -24,8 +30,9 @@ swag init
```
#### Config
```
default username: admin
default username: admin
default password: 123456
```
@ -33,16 +40,17 @@ default password: 123456
[Document](https://github.com/xpunch/go-micro-dashboard/tree/main/frontend)
## Docker
#### Generate Web Files
```
docker run .
go install github.com/UnnoTed/fileb0x@latest
fileb0x b0x.yaml
```
## Docker Compose
## Docker
```
docker-compose up -d
docker run -d --name "go-micro-dashboard" -p "4000:4000" xpunch/go-micro-dashboard
```
### Community

@ -0,0 +1,83 @@
# all folders and files are relative to the path
# where fileb0x was run at!
# default: main
pkg: web
# destination
dest: "./web/"
# gofmt
# type: bool
# default: false
fmt: true
# compress files
# at the moment, only supports gzip
#
# type: object
compression:
# activates the compression
#
# type: bool
# default: false
compress: false
# valid values are:
# -> "NoCompression"
# -> "BestSpeed"
# -> "BestCompression"
# -> "DefaultCompression" or ""
#
# type: string
# default: "DefaultCompression" # when: Compress == true && Method == ""
method: ""
# true = do it yourself (the file is written as gzip compressed file into the memory file system)
# false = decompress files at run time (while writing file into memory file system)
#
# type: bool
# default: false
keep: false
# ---------------
# -- DANGEROUS --
# ---------------
#
# cleans the destination folder (only b0xfiles)
# you should use this when using the spread function
# type: bool
# default: false
clean: true
# default: ab0x.go
output: "ab0x.go"
# [unexporTed] builds non-exporTed functions, variables and types...
# type: bool
# default: false
unexporTed: false
# [spread] means it will make a file to hold all fileb0x data
# and each file into a separaTed .go file
#
# example:
# theres 2 files in the folder assets, they're: hello.json and world.txt
# when spread is activaTed, fileb0x will make a file:
# b0x.go or [output]'s data, assets_hello.json.go and assets_world.txt.go
#
#
# type: bool
# default: false
spread: true
# [lcf] log changed files when spread is active
lcf: true
# type: array of objects
custom:
- files:
- "frontend/dist"
base: "frontend/dist"
exclude:
- "/3rdpartylicenses.txt"

@ -1,11 +0,0 @@
server:
env: "dev"
address: ":4000"
cors:
enable: true
origin: "http://localhost:4200"
swagger:
host: "localhost:4000"
base: "/"
web:
path: "./web"

@ -17,7 +17,6 @@ type ServerConfig struct {
Auth AuthConfig
CORS CORSConfig
Swagger SwaggerConfig
Web WebConfig
}
type AuthConfig struct {
@ -37,10 +36,6 @@ type SwaggerConfig struct {
Base string
}
type WebConfig struct {
Path string
}
func GetConfig() Config {
return *_cfg
}

@ -29,12 +29,9 @@ var _cfg *Config = &Config{
TokenExpiration: 24 * time.Hour,
},
Swagger: SwaggerConfig{
Host: "localhost:4000",
Host: "localhost",
Base: "/",
},
Web: WebConfig{
Path: "web",
},
},
}
@ -43,10 +40,6 @@ func Load() error {
var configor config.Config
var err error
switch strings.ToLower(os.Getenv("CONFIG_TYPE")) {
case "env":
configor, err = config.NewConfig(
config.WithSource(env.NewSource()),
)
case "toml":
filename := "config.toml"
if name := os.Getenv("CONFIG_FILE"); len(name) > 0 {
@ -56,7 +49,7 @@ func Load() error {
config.WithSource(file.NewSource(file.WithPath(filename))),
config.WithReader(json.NewReader(reader.WithEncoder(toml.NewEncoder()))),
)
default:
case "yaml":
filename := "config.yaml"
if name := os.Getenv("CONFIG_FILE"); len(name) > 0 {
filename = name
@ -65,6 +58,10 @@ func Load() error {
config.WithSource(file.NewSource(file.WithPath(filename))),
config.WithReader(json.NewReader(reader.WithEncoder(yaml.NewEncoder()))),
)
default:
configor, err = config.NewConfig(
config.WithSource(env.NewSource()),
)
}
if err != nil {
return errors.Wrap(err, "configor.New")

@ -52,7 +52,7 @@
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "../web",
"outputPath": "dist",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",

@ -16,7 +16,7 @@
<nz-list-item *ngFor="let service of services" nzNoFlex>
<a (click)="gotoServiceDetail(service.name, undefined)">{{ service.name }}</a>
<span *ngFor="let version of service.versions" style="margin-left:8px;">
<nz-tag [nzColor]="'green'"><a (click)="gotoServiceDetail(service.name, version)">{{ version }}</a></nz-tag>
<nz-tag *ngIf="version" [nzColor]="'green'"><a (click)="gotoServiceDetail(service.name, version)">{{ version }}</a></nz-tag>
</span>
</nz-list-item>
<nz-list-empty *ngIf="!services||!services.length"></nz-list-empty>

@ -15,6 +15,7 @@ require (
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/gin-contrib/static v0.0.1
github.com/gin-gonic/gin v1.7.4
github.com/markbates/pkger v0.17.1
github.com/pkg/errors v0.9.1
github.com/swaggo/gin-swagger v1.3.3
github.com/swaggo/swag v1.7.4
@ -47,6 +48,7 @@ require (
github.com/go-playground/locales v0.13.0 // indirect
github.com/go-playground/universal-translator v0.17.0 // indirect
github.com/go-playground/validator/v10 v10.4.1 // indirect
github.com/gobuffalo/here v0.6.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/uuid v1.2.0 // indirect

@ -250,6 +250,8 @@ github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn
github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48/go.mod h1:dZGr0i9PLlaaTD4H/hoZIDjQ+r6xq8mgbRzHZf7f2J8=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b/go.mod h1:Xo4aNUOrJnVruqWQJBtW6+bTBDTniY8yZum5rF3b5jw=
github.com/gobuffalo/here v0.6.0 h1:hYrd0a6gDmWxBM4TnrGw8mQg24iSVoIkHEk7FodQcBI=
github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.0.4/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
@ -430,6 +432,8 @@ github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/markbates/pkger v0.17.1 h1:/MKEtWqtc0mZvu9OinB9UzVN9iYCwLWuyUv4Bw+PCno=
github.com/markbates/pkger v0.17.1/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI=
github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A=
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ=
@ -1015,6 +1019,7 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=

@ -1,9 +1,6 @@
package handler
import (
"path/filepath"
"github.com/gin-contrib/static"
"github.com/gin-gonic/gin"
ginSwagger "github.com/swaggo/gin-swagger"
"github.com/swaggo/gin-swagger/swaggerFiles"
@ -14,6 +11,7 @@ import (
"github.com/xpunch/go-micro-dashboard/handler/registry"
"github.com/xpunch/go-micro-dashboard/handler/route"
"github.com/xpunch/go-micro-dashboard/handler/statistics"
"github.com/xpunch/go-micro-dashboard/web"
"go-micro.dev/v4/client"
)
@ -29,10 +27,9 @@ func Register(opts Options) error {
docs.SwaggerInfo.BasePath = cfg.Swagger.Base
router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
}
router.Use(static.Serve("/", static.LocalFile(config.GetServerConfig().Web.Path, false)))
router.NoRoute(func(c *gin.Context) {
c.File(filepath.Join(config.GetServerConfig().Web.Path, "index.html"))
})
if err := web.RegisterRoute(router); err != nil {
return err
}
if cfg := config.GetServerConfig().CORS; cfg.Enable {
router.Use(CorsHandler(cfg.Origin))
}

@ -0,0 +1,144 @@
// Code generated by fileb0x at "2021-11-22 15:58:09.6945407 +0800 CST m=+0.053806901" from config file "b0x.yaml" DO NOT EDIT.
// modification hash(40921e3ce04e6483775714327fee4445.8be3f833d63e3c844663716446e13a42)
package web
import (
"bytes"
"context"
"io"
"net/http"
"os"
"path"
"golang.org/x/net/webdav"
)
var (
// CTX is a context for webdav vfs
CTX = context.Background()
// FS is a virtual memory file system
FS = webdav.NewMemFS()
// Handler is used to server files through a http handler
Handler *webdav.Handler
// HTTP is the http file system
HTTP http.FileSystem = new(HTTPFS)
)
// HTTPFS implements http.FileSystem
type HTTPFS struct {
// Prefix allows to limit the path of all requests. F.e. a prefix "css" would allow only calls to /css/*
Prefix string
}
func init() {
err := CTX.Err()
if err != nil {
panic(err)
}
err = FS.Mkdir(CTX, "/assets/", 0777)
if err != nil && err != os.ErrExist {
panic(err)
}
Handler = &webdav.Handler{
FileSystem: FS,
LockSystem: webdav.NewMemLS(),
}
}
// Open a file
func (hfs *HTTPFS) Open(path string) (http.File, error) {
path = hfs.Prefix + path
f, err := FS.OpenFile(CTX, path, os.O_RDONLY, 0644)
if err != nil {
return nil, err
}
return f, nil
}
// ReadFile is adapTed from ioutil
func ReadFile(path string) ([]byte, error) {
f, err := FS.OpenFile(CTX, path, os.O_RDONLY, 0644)
if err != nil {
return nil, err
}
buf := bytes.NewBuffer(make([]byte, 0, bytes.MinRead))
// If the buffer overflows, we will get bytes.ErrTooLarge.
// Return that as an error. Any other panic remains.
defer func() {
e := recover()
if e == nil {
return
}
if panicErr, ok := e.(error); ok && panicErr == bytes.ErrTooLarge {
err = panicErr
} else {
panic(e)
}
}()
_, err = buf.ReadFrom(f)
return buf.Bytes(), err
}
// WriteFile is adapTed from ioutil
func WriteFile(filename string, data []byte, perm os.FileMode) error {
f, err := FS.OpenFile(CTX, filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
if err != nil {
return err
}
n, err := f.Write(data)
if err == nil && n < len(data) {
err = io.ErrShortWrite
}
if err1 := f.Close(); err == nil {
err = err1
}
return err
}
// WalkDirs looks for files in the given dir and returns a list of files in it
// usage for all files in the b0x: WalkDirs("", false)
func WalkDirs(name string, includeDirsInList bool, files ...string) ([]string, error) {
f, err := FS.OpenFile(CTX, name, os.O_RDONLY, 0)
if err != nil {
return nil, err
}
fileInfos, err := f.Readdir(0)
if err != nil {
return nil, err
}
err = f.Close()
if err != nil {
return nil, err
}
for _, info := range fileInfos {
filename := path.Join(name, info.Name())
if includeDirsInList || !info.IsDir() {
files = append(files, filename)
}
if info.IsDir() {
files, err = WalkDirs(filename, includeDirsInList, files...)
if err != nil {
return nil, err
}
}
}
return files, nil
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -0,0 +1,50 @@
package web
import (
"path/filepath"
"github.com/gin-gonic/gin"
)
func RegisterRoute(router *gin.Engine) error {
files, err := WalkDirs("", false)
if err != nil {
return err
}
for _, f := range files {
router.GET(f, func(name string) gin.HandlerFunc {
return func(c *gin.Context) {
data, err := ReadFile(name)
if err != nil {
c.AbortWithError(500, err)
return
}
switch filepath.Ext(name) {
case ".html":
c.Header("Content-Type", "text/html; charset=utf-8")
case ".css":
c.Header("Content-Type", "text/css; charset=utf-8")
case ".js":
c.Header("Content-Type", "application/javascript")
}
if _, err := c.Writer.Write(data); err != nil {
c.AbortWithError(500, err)
return
}
}
}(f))
}
router.GET("/", func(c *gin.Context) {
data, err := ReadFile("index.html")
if err != nil {
c.AbortWithError(500, err)
return
}
c.Header("Content-Type", "text/html; charset=utf-8")
if _, err := c.Writer.Write(data); err != nil {
c.AbortWithError(500, err)
return
}
})
return nil
}
Loading…
Cancel
Save