Skip to content

Commit

Permalink
Serve both UI assets and REST API from handler
Browse files Browse the repository at this point in the history
  • Loading branch information
hibiken committed Oct 12, 2021
1 parent b20cf02 commit ccdd6ce
Show file tree
Hide file tree
Showing 35 changed files with 620 additions and 234 deletions.
19 changes: 1 addition & 18 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,29 +36,12 @@ jobs:
- name: Install NPM packages
run: cd ui && rm yarn.lock && yarn install

- name: Build UI Bundle
run: cd ui && yarn build

- name: Create Release Archive
run: tar -czvf ui-assets.tar.gz -C ui/build .

- name: Build release binary
- name: Build Release Binary
run: |
GOOS=${{ matrix.goos }} GOARCH=amd64 make build
tar -czvf asynqmon_${{ steps.release.outputs.tag_name }}_${{ matrix.goos }}_amd64.tar.gz asynqmon
ls
- name: Upload UI Bundle
id: upload-ui-release-asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ github.token }}
with:
upload_url: ${{ steps.release.outputs.upload_url }}
asset_path: ./ui-assets.tar.gz
asset_name: ui-assets.tar.gz
asset_content_type: application/gzip

- name: Upload Release Binary
id: upload-go-release-asset
uses: actions/upload-release-asset@v1
Expand Down
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@ package-json.lock
# main binary
asynqmon
dist/
cmd/asynqmon/ui-assets

# Editor configs
.idea/
.vscode/
.editorconfig

# examples
examples/
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ RUN go mod download
COPY . .

# Copy frontend static files from /static to the root folder of the backend container.
COPY --from=frontend ["/static/build", "ui-assets"]
COPY --from=frontend ["/static/build", "ui/build"]

# Set necessary environmet variables needed for the image and build the server.
ENV CGO_ENABLED=0 GOOS=linux GOARCH=amd64
Expand Down
19 changes: 3 additions & 16 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,22 +1,9 @@
.PHONY: assets sync go_binary build docker
.PHONY: build docker

NODE_PATH ?= $(PWD)/ui/node_modules

assets:
@if [ ! -d "$(NODE_PATH)" ]; then cd ./ui && yarn install --modules-folder $(NODE_PATH); fi
cd ./ui && yarn build --modules-folder $(NODE_PATH)

# sync will copy the ui build assets to cmd/asynqmon so that it can be embedded into the go binary
sync:
rsync -avu --delete "./ui/build/" "./cmd/asynqmon/ui-assets"

# Build go binary.
go_binary: assets sync
# Build a release binary.
build:
go build -o asynqmon ./cmd/asynqmon

# Target to build a release binary.
build: go_binary

# Build image and run Asynqmon server (with default settings).
docker:
docker build -t asynqmon .
Expand Down
121 changes: 69 additions & 52 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@

# A modern web based tool for monitoring & administrating [Asynq](https://github.com/hibiken/asynq) queues, tasks and message broker

## Overview

Asynqmon is both a library that you can include in your web application, as well as a binary that you can simply install and run.

## Version Compatibility

| Asynq version | WebUI (asynqmon) version |
| -------------- | ------------------------ |
| 0.18.x | 0.2.x |
| 0.16.x, 0.17.x | 0.1.x |

## Install
## Install the binary

### Release binaries

Expand Down Expand Up @@ -47,57 +51,7 @@ To build Docker image locally, run:
make docker
```

### Importing into projects

You can import `asynqmon` into other projects and create a single binary to serve other components of `asynq` and `asynqmon` from a single binary.

<details><summary>Example</summary>
<p>

> `staticContents` can be embedded by using the pre-built UI bundle from the Releases section.
```go
package main

import (
"embed"
"log"
"net/http"

"github.com/gorilla/mux"

"github.com/hibiken/asynq"
"github.com/hibiken/asynqmon"
)

//go:embed ui-assets/*
var staticContents embed.FS

func main() {
h := asynqmon.New(asynqmon.Options{
RedisConnOpt: asynq.RedisClientOpt{Addr: ":6379"},
})
defer h.Close()

r := mux.NewRouter()
r.PathPrefix("/api").Handler(h)
// Add static content handler or other handlers
// r.PathPrefix("/").Handler( /* &staticContentHandler{staticContents} */ )

srv := &http.Server{
Handler: r,
Addr: ":8080",
}

log.Fatal(srv.ListenAndServe())
}
```

</p>
</details>


## Run
## Run the binary

To use the defaults, simply run and open http://localhost:8080.

Expand Down Expand Up @@ -171,6 +125,69 @@ Next, go to [localhost:8080](http://localhost:8080) and see Asynqmon dashboard:

![Web UI Settings and adaptive dark mode](https://user-images.githubusercontent.com/11155743/114697149-3517c380-9d26-11eb-9f7a-ae2dd00aad5b.png)

### Importing into projects

Asynqmon is also a library which can be imported into an existing web application.

Example with [net/http](https://pkg.go.dev/net/http):

```go
package main

import (
"log"
"net/http"

"github.com/hibiken/asynq"
"github.com/hibiken/asynqmon"
)

func main() {
h := asynqmon.New(asynqmon.Options{
RootPath: "/monitoring", // RootPath specifies the root for asynqmon app
RedisConnOpt: asynq.RedisClientOpt{Addr: ":6379"},
})

http.Handle(h.RootPath(), h)

// Go to http://localhost:8080/monitoring to see asynqmon homepage.
log.Fatal(http.ListenAndServe(":8000", nil))
}
```

Example with [gorilla/mux](https://pkg.go.dev/github.com/gorilla/mux):

```go
package main

import (
"log"
"net/http"

"github.com/gorilla/mux"
"github.com/hibiken/asynq"
"github.com/hibiken/asynqmon"
)

func main() {
h := asynqmon.New(asynqmon.Options{
RootPath: "/monitoring", // RootPath specifies the root for asynqmon app
RedisConnOpt: asynq.RedisClientOpt{Addr: ":6379"},
})

r := mux.NewRouter()
r.PathPrefix(h.RootPath()).Handler(h)

srv := &http.Server{
Handler: r,
Addr: ":8080",
}

// Go to http://localhost:8080/monitoring to see asynqmon homepage.
log.Fatal(srv.ListenAndServe())
}
```

## License

Copyright (c) 2019-present [Ken Hibino](https://github.com/hibiken) and [Contributors](https://github.com/hibiken/asynqmon/graphs/contributors). `Asynqmon` is free and open-source software licensed under the [MIT License](https://github.com/hibiken/asynq/blob/master/LICENSE). Official logo was created by [Vic Shóstak](https://github.com/koddr) and distributed under [Creative Commons](https://creativecommons.org/publicdomain/zero/1.0/) license (CC0 1.0 Universal).
55 changes: 18 additions & 37 deletions cmd/asynqmon/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@ package main

import (
"crypto/tls"
"embed"
"flag"
"fmt"
"github.com/gorilla/mux"
"log"
"net/http"
"strings"
Expand Down Expand Up @@ -41,7 +39,9 @@ func init() {
flag.StringVar(&flagRedisClusterNodes, "redis-cluster-nodes", "", "comma separated list of host:port addresses of cluster nodes")
}

func getRedisOptionsFromFlags() (*redis.UniversalOptions, error) {
// TODO: Write test and refactor this code.
// IDEA: https://eli.thegreenplace.net/2020/testing-flag-parsing-in-go-programs/
func getRedisOptionsFromFlags() (asynq.RedisConnOpt, error) {
var opts redis.UniversalOptions

if flagRedisClusterNodes != "" {
Expand Down Expand Up @@ -74,59 +74,40 @@ func getRedisOptionsFromFlags() (*redis.UniversalOptions, error) {
opts.TLSConfig.InsecureSkipVerify = true
}

return &opts, nil
if flagRedisClusterNodes != "" {
return asynq.RedisClusterClientOpt{
Addrs: opts.Addrs,
Password: opts.Password,
TLSConfig: opts.TLSConfig,
}, nil
}
return asynq.RedisClientOpt{
Addr: opts.Addrs[0],
DB: opts.DB,
Password: opts.Password,
TLSConfig: opts.TLSConfig,
}, nil
}

//go:embed ui-assets/*
var staticContents embed.FS

func main() {
flag.Parse()

opts, err := getRedisOptionsFromFlags()
redisConnOpt, err := getRedisOptionsFromFlags()
if err != nil {
log.Fatal(err)
}

useRedisCluster := flagRedisClusterNodes != ""

var redisConnOpt asynq.RedisConnOpt
if useRedisCluster {
redisConnOpt = asynq.RedisClusterClientOpt{
Addrs: opts.Addrs,
Password: opts.Password,
TLSConfig: opts.TLSConfig,
}
} else {
redisConnOpt = asynq.RedisClientOpt{
Addr: opts.Addrs[0],
DB: opts.DB,
Password: opts.Password,
TLSConfig: opts.TLSConfig,
}
}

h := asynqmon.New(asynqmon.Options{
RedisConnOpt: redisConnOpt,
})
defer h.Close()

r := mux.NewRouter()
r.PathPrefix("/api").Handler(h)
r.PathPrefix("/").Handler(&staticContentHandler{
contents: staticContents,
staticDirPath: "ui-assets",
indexFileName: "index.html",
})

r.Use(loggingMiddleware)

c := cors.New(cors.Options{
AllowedMethods: []string{"GET", "POST", "DELETE"},
})

srv := &http.Server{
Handler: c.Handler(r),
Handler: c.Handler(h),
Addr: fmt.Sprintf(":%d", flagPort),
WriteTimeout: 10 * time.Second,
ReadTimeout: 10 * time.Second,
Expand Down
58 changes: 0 additions & 58 deletions cmd/asynqmon/static.go

This file was deleted.

Loading

0 comments on commit ccdd6ce

Please sign in to comment.