Giter Site home page Giter Site logo

asynqmon's Introduction

Asynqmon logo

Web UI for monitoring & administering Asynq task queue

Overview

Asynqmon is a web UI tool for monitoring and administering Asynq queues and tasks. It supports integration with Prometheus to display time-series data.

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

Please make sure the version compatibility with the Asynq package you are using.

Asynq version WebUI (asynqmon) version
0.23.x 0.7.x
0.22.x 0.6.x
0.20.x, 0.21.x 0.5.x
0.19.x 0.4.x
0.18.x 0.2.x, 0.3.x
0.16.x, 0.17.x 0.1.x

Install the binary

There're a few options to install the binary:

Release binaries

You can download the release binary for your system from the releases page.

Docker image

To pull the Docker image:

# Pull the latest image
docker pull hibiken/asynqmon

# Or specify the image by tag
docker pull hibiken/asynqmon[:tag]

Building from source

To build Asynqmon from source code, make sure you have Go installed (download). Version 1.16 or higher is required. You also need Node.js and Yarn installed in order to build the frontend assets.

Download the source code of this repository and then run:

make build

The asynqmon binary should be created in the current directory.

Building Docker image locally

To build Docker image locally, run:

make docker

Run the binary

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

# with a binary
./asynqmon

# with a docker image
docker run --rm \
    --name asynqmon \
    -p 8080:8080 \
    hibiken/asynqmon

By default, Asynqmon web server listens on port 8080 and connects to a Redis server running on 127.0.0.1:6379.

To see all available flags, run:

# with a binary
./asynqmon --help

# with a docker image
docker run hibiken/asynqmon --help

Here's the available flags:

Note: Use --redis-url to specify address, db-number, and password with one flag value; Alternatively, use --redis-addr, --redis-db, and --redis-password to specify each value.

Flag Env Description Default
--port(int) PORT port number to use for web ui server 8080
---redis-url(string) REDIS_URL URL to redis or sentinel server. See godoc for supported format ""
--redis-addr(string) REDIS_ADDR address of redis server to connect to "127.0.0.1:6379"
--redis-db(int) REDIS_DB redis database number 0
--redis-password(string) REDIS_PASSWORD password to use when connecting to redis server ""
--redis-cluster-nodes(string) REDIS_CLUSTER_NODES comma separated list of host:port addresses of cluster nodes ""
--redis-tls(string) REDIS_TLS server name for TLS validation used when connecting to redis server ""
--redis-insecure-tls(bool) REDIS_INSECURE_TLS disable TLS certificate host checks false
--enable-metrics-exporter(bool) ENABLE_METRICS_EXPORTER enable prometheus metrics exporter to expose queue metrics false
--prometheus-addr(string) PROMETHEUS_ADDR address of prometheus server to query time series ""
--read-only(bool) READ_ONLY use web UI in read-only mode false

Connecting to Redis

To connect to a single redis server, use either --redis-url or (--redis-addr, --redis-db, and --redis-password).

Example:

$ ./asynqmon --redis-url=redis://:mypassword@localhost:6380/2

$ ./asynqmon --redis-addr=localhost:6380 --redis-db=2 --redis-password=mypassword

To connect to redis-sentinels, use --redis-url.

Example:

$ ./asynqmon --redis-url=redis-sentinel://:mypassword@localhost:5000,localhost:5001,localhost:5002?master=mymaster

To connect to a redis-cluster, use --redis-cluster-nodes.

Example:

$ ./asynqmon --redis-cluster-nodes=localhost:7000,localhost:7001,localhost:7002,localhost:7003,localhost:7004,localhost:7006

Integration with Prometheus

The binary supports two flags to enable integration with Prometheus.

First, enable metrics exporter to expose queue metrics to Prometheus server by passing --enable-metrics-exporter flag. The metrics data is now available under /metrics for Prometheus server to scrape.

Once the metrics data is collected by a Prometheus server, you can pass the address of the Prometheus server to asynqmon to query the time-series data. The address can be specified via --prometheus-addr. This enables the metrics view on the Web UI.

Screen Shot 2021-12-19 at 4 37 19 PM

Examples

# with a local binary; custom port and connect to redis server at localhost:6380
./asynqmon --port=3000 --redis-addr=localhost:6380

# with prometheus integration enabled
./asynqmon --enable-metrics-exporter --prometheus-addr=http://localhost:9090

# with Docker (connect to a Redis server running on the host machine)
docker run --rm \
    --name asynqmon \
    -p 3000:3000 \
    hibiken/asynqmon --port=3000 --redis-addr=host.docker.internal:6380

# with Docker (connect to a Redis server running in the Docker container)
docker run --rm \
    --name asynqmon \
    --network dev-network \
    -p 8080:8080 \
    hibiken/asynqmon --redis-addr=dev-redis:6379

Next, go to localhost:8080 and see Asynqmon dashboard:

Web UI Queues View

Tasks view

Web UI TasksView

Settings and adaptive dark mode

Web UI Settings and adaptive dark mode

Import as a Library

GoDoc

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

Example with net/http:

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"},
	})

    // Note: We need the tailing slash when using net/http.ServeMux.
	http.Handle(h.RootPath()+"/", h)

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

Example with gorilla/mux:

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())
}

Example with labstack/echo):

package main

import (
	"github.com/labstack/echo/v4"
	"github.com/hibiken/asynq"
	"github.com/hibiken/asynqmon"
)

func main() {
        e := echo.New()

	mon := asynqmon.New(asynqmon.Options{
		RootPath: "/monitoring/tasks",
		RedisConnOpt: asynq.RedisClientOpt{
			Addr: ":6379",
			Password: "",
			DB: 0,
		},
	})
	e.Any("/monitoring/tasks/*", echo.WrapHandler(mon))
	e.Start(":8080")
}

License

Copyright (c) 2019-present Ken Hibino and Contributors. Asynqmon is free and open-source software licensed under the MIT License. Official logo was created by Vic Shóstak and distributed under Creative Commons license (CC0 1.0 Universal).

asynqmon's People

Contributors

ajatprabha avatar arnezsng avatar dependabot[bot] avatar gaffneyc avatar hibiken avatar imhugofonseca avatar jiangdi0924 avatar koddr avatar lambdagirl avatar lukasmalkmus avatar safaci2000 avatar stellarisw avatar trungdlp-wolffun avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

asynqmon's Issues

Add a server path option to proxy the asynqmon

For example there is an application running at port 3000, asynqmon running at port 3001. We want the path /asynqmon to proxy to port 3001, which now is impossible because of the path prefix unmatch.

Can not build image from Dockerfile. Can not connect, Error Couldn't find a package.json file in "/static"

Hi, is there any way, to build and link to redis from docker-compose?
I tried everything, can not connect to redis.

Even like this:

docker run -d --rm
--name asynqmon
-p 8080:8080
hibiken/asynqmon --redis-addr=172.18.0.4:6379 Can not connect to redis.

but if i try like this: redis-cli -h 172.18.0.4 -p 6379 ping, everything is ok.
I am trying to build new docker image from Dockerfile, but have following issue:

Building asynqmon
Step 1/16 : FROM alpine:3.13 AS frontend
3.13: Pulling from library/alpine
4e9f2cdf4387: Pull complete
Digest: sha256:2582893dec6f12fd499d3a709477f2c0c0c1dfcd28024c93f1f0626b9e3540c8
Status: Downloaded newer image for alpine:3.13
 ---> 12adea71a33b
Step 2/16 : WORKDIR /static
 ---> Running in ba029a731c1d
Removing intermediate container ba029a731c1d
 ---> 23be42dab112
Step 3/16 : RUN apk add --no-cache npm &&     npm i -g -s --unsafe-perm yarn
 ---> Running in 4e94d9f6516b
fetch https://dl-cdn.alpinelinux.org/alpine/v3.13/main/x86_64/APKINDEX.tar.gz
fetch https://dl-cdn.alpinelinux.org/alpine/v3.13/community/x86_64/APKINDEX.tar.gz
(1/8) Installing ca-certificates (20191127-r5)
(2/8) Installing nghttp2-libs (1.42.0-r1)
(3/8) Installing brotli-libs (1.0.9-r3)
(4/8) Installing c-ares (1.17.2-r0)
(5/8) Installing libgcc (10.2.1_pre1-r3)
(6/8) Installing libstdc++ (10.2.1_pre1-r3)
(7/8) Installing nodejs (14.17.6-r0)
(8/8) Installing npm (14.17.6-r0)
Executing busybox-1.32.1-r6.trigger
Executing ca-certificates-20191127-r5.trigger
OK: 71 MiB in 22 packages
/usr/bin/yarn -> /usr/lib/node_modules/yarn/bin/yarn.js
/usr/bin/yarnpkg -> /usr/lib/node_modules/yarn/bin/yarn.js
+ [email protected]
added 1 package in 2.147s
Removing intermediate container 4e94d9f6516b
 ---> cef867ea8aea
Step 4/16 : COPY ui .
 ---> b4258e2afff3
Step 5/16 : RUN yarn install && yarn build
 ---> Running in babaf53cdef4
yarn install v1.22.15
info No lockfile found.
[1/4] Resolving packages...
[2/4] Fetching packages...
[3/4] Linking dependencies...
[4/4] Building fresh packages...
success Saved lockfile.
Done in 0.12s.
yarn run v1.22.15
error Couldn't find a package.json file in "/static"

error Couldn't find a package.json file in "/static"

Building asynqmon
Step 1/16 : FROM alpine:3.13 AS frontend
3.13: Pulling from library/alpine
4e9f2cdf4387: Pull complete
Digest: sha256:2582893dec6f12fd499d3a709477f2c0c0c1dfcd28024c93f1f0626b9e3540c8
Status: Downloaded newer image for alpine:3.13
---> 12adea71a33b
Step 2/16 : WORKDIR /static
---> Running in ba029a731c1d
Removing intermediate container ba029a731c1d
---> 23be42dab112
Step 3/16 : RUN apk add --no-cache npm && npm i -g -s --unsafe-perm yarn
---> Running in 4e94d9f6516b
fetch https://dl-cdn.alpinelinux.org/alpine/v3.13/main/x86_64/APKINDEX.tar.gz
fetch https://dl-cdn.alpinelinux.org/alpine/v3.13/community/x86_64/APKINDEX.tar.gz
(1/8) Installing ca-certificates (20191127-r5)
(2/8) Installing nghttp2-libs (1.42.0-r1)
(3/8) Installing brotli-libs (1.0.9-r3)
(4/8) Installing c-ares (1.17.2-r0)
(5/8) Installing libgcc (10.2.1_pre1-r3)
(6/8) Installing libstdc++ (10.2.1_pre1-r3)
(7/8) Installing nodejs (14.17.6-r0)
(8/8) Installing npm (14.17.6-r0)
Executing busybox-1.32.1-r6.trigger
Executing ca-certificates-20191127-r5.trigger
OK: 71 MiB in 22 packages
/usr/bin/yarn -> /usr/lib/node_modules/yarn/bin/yarn.js
/usr/bin/yarnpkg -> /usr/lib/node_modules/yarn/bin/yarn.js

  • [email protected]
    added 1 package in 2.147s
    Removing intermediate container 4e94d9f6516b
    ---> cef867ea8aea
    Step 4/16 : COPY ui .
    ---> b4258e2afff3
    Step 5/16 : RUN yarn install && yarn build
    ---> Running in babaf53cdef4
    yarn install v1.22.15
    info No lockfile found.
    [1/4] Resolving packages...
    [2/4] Fetching packages...
    [3/4] Linking dependencies...
    [4/4] Building fresh packages...
    success Saved lockfile.
    Done in 0.12s.
    yarn run v1.22.15
    error Couldn't find a package.json file in "/static"

Environment variables as an alternative to cmd flags

Environment variables would make this easy to deploy in an environment such as Heroku or Cloud Run without any modifications. So I propose for asynqmon to read environment variables in case the corresponding flag is not set.

Related to #150

UNKNOWN: UNKNOWN: redis eval error: ERR Error running script

Hi guys, I'm using as an library. It's run fine in my lap but fail on remote. Can I know what is the root cause of this? Thank you so much.

Stacktrade:
UNKNOWN: UNKNOWN: redis eval error: ERR Error running script (call to f_d2725d6f7a40c52b2534b2526b30a74d28fe023e): @user_script:21: user_script:21: attempt to perform arithmetic on local 'm' (a string value)

Error UI in Safari and Chrome

Hi, the logo in Safari and Chrom is now broken.

And the menu set is different in Safari and Chrome.

While I'm trying to run ui from a local machine and everything comes down to the impossibility of connecting to redis, I'm trying to figure it out.

mostly errors:
"GET / api / redis_info HTTP / 1.1" 500 53

tls: DialWithDialer timed out

But I checked the connection from the local machine and everything works, oddities)
Снимок экрана 2021-05-08 в 23 38 39
Снимок экрана 2021-05-08 в 23 38 50

Create a formula to installation from Homebrew

Hi,

I think, better way to delivery Asynqmon to macOS/Linux users is a regular Homebrew formula. You can place it to the "tap repository" called, for example, hibiken/homebrew-asynqmon (this name of repository is required for Homebrew tap formulas).

OK, our formula in ./Formula/tap.rb is a simple Ruby file with this code:

# ./github.com/hibiken/homebrew-asynqmon/Formula/tap.rb

class Asynqmon < Formula
  desc "Asynqmon is a web based tool for monitoring and administrating Asynq queues and tasks."
  homepage "https://github.com/hibiken/asynqmon"
  version "v0.2.0"
  license "MIT"

  if OS.mac? && Hardware::CPU.intel?
    url "https://github.com/hibiken/asynqmon/releases/download/v0.2.0/asynqmon_v0.2.0_macOS_x86_64.tar.gz"
    sha256 "<HASH>" # <-- checksum hash for this file here
  end

  if OS.mac? && Hardware::CPU.arm?
    url "https://github.com/hibiken/asynqmon/releases/download/v0.2.0/asynqmon_v0.2.0_macOS_arm64.tar.gz"
    sha256 "<HASH>" # <-- checksum hash for this file here
  end

  if OS.linux? && Hardware::CPU.intel?
    url "https://github.com/hibiken/asynqmon/releases/download/v0.2.0/asynqmon_v0.2.0_Linux_x86_64.tar.gz"
    sha256 "<HASH>" # <-- checksum hash for this file here
  end

  def install
    bin.install "asynqmon"
  end

  test do
    system "#{bin}/asynqmon", "--help"
  end

  def caveats; <<~EOS
    Asynqmon server needs to connect to Redis server to serve data.

    To start a default server, run `asynqmon` and open http://localhost:8080
    To see all available flags, run `asynqmon --help` command.
  EOS
  end
end

☝️ Please note, this example for v0.2.0 version and without checksum hashes for each tar.gz file.

Next steps:

  • Push this file to the ./Formula folder in the github.com/hibiken/homebrew-asynqmon repository.
  • Make a new release in Asynqmon's core repository (github.com/hibiken/asynqmon).
  • Upload Asynqmon binary in tar.gz archives to the release.
  • Create checksum.txt file and upload it to the release.

And we're ready! Just tap a new formula:

brew tap hibiken/asynqmon

And install Asynqmon as regular Homebrew bottle:

brew install hibiken/asynqmon/tap

That's it! 🎉

Then you just need to update this formula file with new checksum hashes and versions after a new Asynqmon release.

Yes, you can simplify this process by using the solution from GoReleaser, but I think that it would be faster.

What do you think about all of this @hibiken ?

Bad module name

The module name in go.mod should be github.com/hibiken/asynqmon instead of asynq. I would love to see this fixed as this makes it possible to tag q version of this tool with tools.go. I can also draft a PR.

Add some user auth to dashboard

Hi,

I think is a good practice to cover Asynqmon dashboard with some auth procedure to login. A common case is when such tools are put side by side in a Docker container and need to be accessed remotely over the network.

For example, we can set this username and password by environment variables at starting Docker container:

docker run --rm \
    --name asynqmon \
    -e ASYNQMON_USER=user \
    -e ASYNQMON_PASSWORD=password \
    -p 8080:8080 \
    hibiken/asynqmon

And catch them by simple os.Getenv function in backend.
Yes, it's very-very simple solution, but works and solves all needs on the topic.

Login form can looks like this (without links, only login+pass+button, of course):
https://material-ui.com/getting-started/templates/sign-in/

What do you think about this, @hibiken? 😉

P.S. also, we can make a little hard thing: separation of users into roles (e.g. a super-admin and a guest, like RabbitMQ does). But that's a start for the future!

Not working on Windows

Hi, great project. I'm trying using it on Windows, but can't get it to work.

When running and trying to access the browser, the following error is displayed:
open ui\build\index.html: file does not exist

I have done some diging and have found the problem, but don't know the right "fix", so for this reason I'm opening this issue instead of PR.

Basically on this line, filepath.Abs returns C:\

path, err := filepath.Abs(r.URL.Path)

This causes problems with the check if is /

if path == "/" {

There is a problem with filepath.Join as well, since it will use \ and GO embed.FS will not find the file then.
https://golang.org/pkg/embed/#hdr-Directives

The path separator is a forward slash, even on Windows systems.

The only way I could get it to work was making the following changes:

p := r.URL.Path
if filepath.IsAbs(p) {
	p = srv.indexFilePath()
} else {
	p = path.Join(srv.staticDirPath, p)
}

// ...
func (srv *staticFileServer) indexFilePath() string {
	return path.Join(srv.staticDirPath, srv.indexFileName)
}

Missing cluster configuration

The code indicates that the only way to connect asynqmon to redis is through a single client connection. This doesn't seem to work when dealing with a cluster setup.

Perhaps if you strings.Split the address string and detect multiple IPs, you can then set up the connection using the cluster connection configs.

Could not retreive servers live data.

Hi, I have a problem with asynqmon:

Error
Could not retreive servers live data — See the logs for details

But I can see the redis server info from dashboard.

image
image
image

Command:
asynqmon --redis-addr=center.xxxxxxxxx:6380 --redis-db=1 --redis-password=xxxxxxxxxxxxxx

Asynq version:
v0.18.6

Asynqmon version:
0.2.3

Any idea? Thank you.

Unable to host dashboard through echo http framework

I will try to keep it simple and short

  • Using echo http framework
  • Redis is up and running
  • When trying to hit localhost:8080/events/dashboard/ blank page is returned with multiple 404 responses as shown below
    • "/events/dashboard/static/js/2.980b0c32.chunk.js"
    • "/events/dashboard/static/js/main.090c4a40.chunk.js"
    • "/events/dashboard/apple-touch-icon.png"

Check the code below

// events queue 
mon := asynqmon.New(asynqmon.Options{
		RootPath:     "/events/dashboard",
		RedisConnOpt: asynq.RedisClientOpt{Addr: ":6379"},

// registering endpoint
e.GET(mon.RootPath(), func(c echo.Context) error {
		c.Response().Header().Set("Content-Type", "text/html; charset=utf-8")
		c.Response().Header().Set("Transfer-Encoding", "chunked")
		mon.ServeHTTP(c.Response(), c.Request())
		return nil
})

OR

// events queue 
mon := asynqmon.New(asynqmon.Options{
		RootPath:     "/events/dashboard",
		RedisConnOpt: asynq.RedisClientOpt{Addr: ":6379"},

// registering endpoint
e.GET(mon.RootPath(), echo.WrapHandler(mon))

[BUG] redis eval error: "this Redis command is not allowed from scripts"

Hi,

locally the asynqmon works perfectly, however now that I just deployed it to my heroku setup (using a redis enterprise cloud instance the chrome console log returns these errors:

UNKNOWN: UNKNOWN: redis eval error: ERR Error running script 
(call to f_7574d838a4ddc18d626139a5521903c904f1404a): 
@user_script:19: @user_script: 19: This Redis command is not allowed from scripts

I googled a bit and this is most likely due to the REDIS server being relatively strict.
Any idea how to circumvent this through an asynqmon configuration (because on the redis enterprise cloud I don't see any EVAL related configurations)

Greetings,
PJ

PS: love your queuing tool and have been in the game for quite some time now :)!

Getting certificate error when connecting to elasticache redis

Setup

Dockerfile

FROM alpine:3.6 as alpine

RUN apk add -U --no-cache ca-certificates

FROM hibiken/asynqmon:0.2.2 as asynqmon
COPY --from=alpine /etc/ssl/certs/ca-certificates.crt /etc/ssl/cert

EXPOSE 3000
ENTRYPOINT [ "./asynqmon", "--redis-url=rediss://xxxx", "--redis-password=xxxxx", "--port=3000"  ]

Error

500 (Internal Server Error): x509: certificate signed by unknown authority

overrideMethod @ react_devtools_backend.js:2850

Would u know on how to fix this? I tried doing this https://stackoverflow.com/questions/52601404/aws-ses-error-x509-certificate-signed-by-unknown-authority ? which explains the dockerfile above

Chart tooltips in dark mode

Currently, the chart tooltips in dark mode is not respecting the theme settings. They should be have a darker background color in dark mode.

got a 502 when state has tasks

my asynqmon version is 0.20, and asynq version is 0.18, when a state have tasks, it will show 502 error page, but have no tasks is ok
screenshot-20210702-120603
the panic log is below
screenshot-20210702-155824

Make it importable as a package

This is a Feature Request: It would be awesome to make this package importable, so I can mount the endpoints onto my own router. If you are ok with this, I would love to draft a PR or at least track some ideas in this issue.

The idea is, that I have a very simple API and would like to run asynqmon alongside it, from the same app. This is way easier then going for a complicated setup (two apps + proxy).

Usage would look something like this: r.Handler(asynqmon.Handler()).

Tell me if that is something you would like to see as well.

queue not exist

my worker has up but queues not showing, whats the problem?

image
image
image

Thanks

Only redis

Hey,

I checked out the repository and one is a great app. I couldn't find a description of whether the application only works with redis db? I'm using NodeJs and Ubuntu 20.04 and I'm looking for something to manage the redis on the server for which this app would be great.

If all is true this only works with https://github.com/hibiken/asynqmon, doesn't it?

Thnaks a lot!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.