Code Monkey home page Code Monkey logo

wg-gen-web's Introduction

Wg Gen Web

Simple Web based configuration generator for WireGuard

Simple Web based configuration generator for WireGuard.

Go Report Card License: WTFPL Discord Build docker images via buildx GitHub last commit Docker Pulls GitHub go.mod Go version GitHub code size in bytes

Why another one ?

All WireGuard UI implementations are trying to manage the service by applying configurations and creating network rules. This implementation only generates configuration and its up to you to create network rules and apply configuration to WireGuard. For example by monitoring generated directory with inotifywait.

The goal is to run Wg Gen Web in a container and WireGuard on host system.

Features

  • Self-hosted and web based
  • Automatically select IP from the network pool assigned to client
  • QR-Code for convenient mobile client configuration
  • Sent email to client with QR-code and client config
  • Enable / Disable client
  • Generation of wg0.conf after any modification
  • IPv6 ready
  • User authentication (Oauth2 OIDC)
  • Dockerized
  • Pretty cool look

Screenshot

Running

Docker

The easiest way to run Wg Gen Web is using the container image

docker run --rm -it -v /tmp/wireguard:/data -p 8080:8080 -e "WG_CONF_DIR=/data" vx3r/wg-gen-web:latest

Docker compose snippet, used for demo server, wg-json-api service is optional

version: '3.6'
services:
  wg-gen-web-demo:
    image: vx3r/wg-gen-web:latest
    container_name: wg-gen-web-demo
    restart: unless-stopped
    expose:
      - "8080/tcp"
    environment:
      - WG_CONF_DIR=/data
      - WG_INTERFACE_NAME=wg0.conf
      - SMTP_HOST=smtp.gmail.com
      - SMTP_PORT=587
      - [email protected]
      - SMTP_PASSWORD=******************
      - SMTP_FROM=Wg Gen Web <[email protected]>
      - OAUTH2_PROVIDER_NAME=github
      - OAUTH2_PROVIDER=https://github.com
      - OAUTH2_CLIENT_ID=******************
      - OAUTH2_CLIENT_SECRET=******************
      - OAUTH2_REDIRECT_URL=https://wg-gen-web-demo.127-0-0-1.fr
    volumes:
      - /etc/wireguard:/data
  wg-json-api:
    image: james/wg-api:latest
    container_name: wg-json-api
    restart: unless-stopped
    cap_add:
      - NET_ADMIN
    network_mode: "host"
    command: wg-api --device wg0 --listen <API_LISTEN_IP>:8182

Please note that mapping /etc/wireguard to /data inside the docker, will erase your host's current configuration. If needed, please make sure to backup your files from /etc/wireguard.

A workaround would be to change the WG_INTERFACE_NAME to something different, as it will create a new interface (wg-auto.conf for example), note that if you do so, you will have to adapt your daemon accordingly.

To get the value for <API_LISTEN_IP> take a look at the WireGuard Status Display section. If the status display should be disabled, remove the whole service from the docker-compose file or use 127.0.0.1 as <API_LISTEN_IP>.

Directly without docker

Fill free to download latest artifacts from my GitLab server:

Put everything in one directory, create .env file with all configurations and run the backend.

Automatically apply changes to WireGuard

Using systemd

Using systemd.path monitor for directory changes see systemd doc

# /etc/systemd/system/wg-gen-web.path
[Unit]
Description=Watch /etc/wireguard for changes

[Path]
PathModified=/etc/wireguard

[Install]
WantedBy=multi-user.target

This .path will activate unit file with the same name

# /etc/systemd/system/wg-gen-web.service
[Unit]
Description=Reload WireGuard
After=network.target

[Service]
Type=oneshot
ExecStart=/usr/bin/systemctl reload [email protected]

[Install]
WantedBy=multi-user.target

Which will reload WireGuard service

Using inotifywait

For any other init system, create a daemon running this script

#!/bin/sh
while inotifywait -e modify -e create /etc/wireguard; do
  wg-quick down wg0
  wg-quick up wg0
done

How to use with existing WireGuard configuration

After first run Wg Gen Web will create server.json in data directory with all server informations.

Feel free to modify this file in order to use your existing keys

What is out of scope

  • Generation or application of any iptables or nftables rules
  • Application of configuration to WireGuard by Wg Gen Web itself

Authentication

Wg Gen Web can use Oauth2 OpenID Connect provider to authenticate users. Currently there are 4 implementations:

  • fake not a real implementation, use this if you don't want to authenticate your clients.

Add the environment variable:

OAUTH2_PROVIDER_NAME=fake
  • github in order to use GitHub as Oauth2 provider.

Add the environment variable:

OAUTH2_PROVIDER_NAME=github
OAUTH2_PROVIDER=https://github.com
OAUTH2_CLIENT_ID=********************
OAUTH2_CLIENT_SECRET=********************
OAUTH2_REDIRECT_URL=https://wg-gen-web-demo.127-0-0-1.fr
  • google in order to use Google as Oauth2 provider. Not yet implemented
help wanted
  • oauth2oidc in order to use RFC compliant Oauth2 OpenId Connect provider.

Add the environment variable:

OAUTH2_PROVIDER_NAME=oauth2oidc
OAUTH2_PROVIDER=https://gitlab.com
OAUTH2_CLIENT_ID=********************
OAUTH2_CLIENT_SECRET=********************
OAUTH2_REDIRECT_URL=https://wg-gen-web-demo.127-0-0-1.fr

Wg Gen Web will only access your profile to get email address and your name, no other unnecessary scopes will be requested.

WireGuard Status Display

Wg Gen Web integrates a WireGuard API implementation to display client stats. In order to enable the Status API integration, the following settings need to be configured:

# https://github.com/jamescun/wg-api integration
WG_STATS_API=http://<API_LISTEN_IP>:8182

# Optional: Token Auth
WG_STATS_API_TOKEN=

# Optional: Basic Auth
WG_STATS_API_USER=
WG_STATS_API_PASS=

To setup the WireGuard API take a look at https://github.com/jamescun/wg-api/blob/master/README.md, or simply use the provided docker-compose file from above.

API_LISTEN_IP

Due to the fact that the wg-api container operates on the host network, the wg-gen-web container cannot directly talk to the API. Thus the docker-host gateway IP of the wg-gen-web container has to be used. If the default bridge network (docker0) is used, this IP should be 172.17.0.1. If a custom network is used, you can find the gateway IP by inspecting the output of:

docker network inspect <network name>

Use the IP address found for Gateway as the API_LISTEN_IP.

Please feel free to test and report any bugs.

Need Help

  • Join us on Discord
  • Create an issue

Development

Backend

From the top level directory run

$ go run main.go

Frontend

Inside another terminal session navigate into the ui folder

$ cd ui

Install required dependencies

$ npm install

Set the base url for the api

$ export VUE_APP_API_BASE_URL=http://localhost:8080/api/v1.0

Start the development server. It will rebuild and reload the site once you make a change to the source code.

$ npm run serve

Now you can access the site from a webbrowser with the url http://localhost:8081.

Application stack

License

wg-gen-web's People

Contributors

aniston avatar asymmetric avatar h44z avatar jeremybolster avatar jrester avatar sphen13 avatar thatanonyg avatar vx3r avatar washcycle avatar xiki808 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  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

wg-gen-web's Issues

Simple page refresh updates wg0.conf

Hi, I installed this service successfully and configured systemd to monitor /etc/wireguard to reload wireguard configuration as per your instructions in the Readme. But it seems that on every simple GET of the web ui, without changing any setting wg0.conf gets updated and therefore wireguard restarted. Is this the expected behavior? Isn't this a problem because of service interruption? Thanks for this nice gui!

docker-compose example, not expose just ports

in the readme, the example docker compose file
expose is for inner stuff I think, ports is how the browser gets inside

current:

version: '3.6'
services:
  wg-gen-web:
    image: vx3r/wg-gen-web:latest
    container_name: wg-gen-web
    restart: unless-stopped
    expose:
      - "8080/tcp"

I think it should be

version: '3.6'
services:
  wg-gen-web:
    image: vx3r/wg-gen-web:latest
    container_name: wg-gen-web
    restart: unless-stopped
    ports:
      - 8080:8080

Wanna work together and add this to my installer?

Hey I have a wireguard installer with over 2500 clones and 300 stars and I think this would be an amazing side thing to add to that so instead of a CLI only users would be able to use a GUI too.

What do you think about it??

[feature request] downloaded file specific name

When downloading a configuration file, it is named wg0.conf.

Would it be possible to generate a filename relevant to the name of the connection? For instance

  • homehome.conf
  • a name with spacesa-name-with-spaces.conf

This will allow to differentiate the various config files, abd avoid the wg0 (1).conf, wg0 (2).conf, ... on Windows (which are not understood by Wireguard on Windows)

empty PresharedKey value breaks WG

Inserting empty: "PresharedKey=" to each peer causes wg to break:

[#] echo WireGuard PreUp
WireGuard PreUp
[#] ip link add wg0 type wireguard
[#] wg setconf wg0 /dev/fd/63
Line unrecognized: `PresharedKey='
Configuration parsing error
[#] ip link delete dev wg0

When upgrading wg-gen-web from a previous version, the clients do not include a preshared key.

Google-SSO

Does this project implement Google SSO ,will it be added in the future ?

Format server.json file

First of all - thank you very much for this great interface to WireGuard. It makes the management of configurations so much easier.

It would be worthwhile I believe to have server.json pretty-printed, instead of the raw version today → it would make it way easier to edit if needed.

{
    "name": "Created with default values",
    "created": "2020-01-31T15:05:13.522502946Z",
    "updated": "2020-01-31T15:07:03.521605467Z",
    "address": "fd9f:6666::10:6:6:1/112, 10.6.6.1/24",
    "listenPort": 51820,
    "privateKey": "gM...=",
    "publicKey": "C4...=",
    "presharedKey": "9wzD/gZHeTD2Bkco2wYWXq21pZJHMSweuUbzFSFNv4I=",
    "endpoint": "wireguard.example.com:123",
    "persistentKeepalive": 16,
    "dns": "fd9f::10:0:0:2, 10.0.0.2"
}

Thanks again for sharing wg-gen-web.

FQDN Supports

I tried config vpn server with dns load balancing, but when I set peers to sub.ourdomain.net, wireguard never get handshake..

So different when by default set to IP Address..

Wireguard correctly translate our A Record, but never get handshakes...

Thank You!

Optional SMTP

I really like having the option of using an SMTP server to send out configurations.

But for my setup, I don't need one and therefore it is somewhat annoying to have the email field for a new client be mandatory.

Can the field be made optional?
Or maybe have some env variable that turns off SMTP and with that removes the field?

Current version

Would it be possible to add somewhere down the web frontend an information about the version (or commit)?

How to restrict/authorize Openid connect users?

Hi ,
I have configured Gitlab as openid connect idp provider, however all authenticated users of Gitlab can now access wg gen web application. I want to restrict access based on some authenticated gitlab users or based on members of authorized Gitlab group.

Can this be done based on docker environment variable while starting the container?

Per Client PersistentKeepalive

Hey, awesome project, I'm really interested in using it for my Wireguard setup.

Sadly I ran into a little issue that is troublesome for my configuration.

I don't want to use a PersistentKeepalive for most clients, as they are mobile devices that don't require it.
But 2 devices are stationary and host services that I want to have available in the VPN network, thus I need a PersistentKeepalive so that they automatically reconnect after a connection drop.

In short: I would like to have PersistentKeepalive be an option that can be set or overwritten per client instead of just globally.

Specify IP address for clients

It looks like the client IPs are generated when you add a client. Provide the option to specify this and / or change it.

DNS setting is not saved

Hi,
Thank for this project. Simple, efficient, easy to run on my nas in a docker compose stack.

Anyway, I have a small issue:
I can't give a DNS on server configuration. wg0.conf is updated, with a timestamp. But DNS field is still missing

++ Simulot

EMail sending using smtp.gmail.com

Hi using the settings for gmail and getting 500 internal server error

      - SMTP_HOST=smtp.gmail.com
      - SMTP_PORT=465
      - [email protected]
      - SMTP_PASSWORD="blahblahblah"
      - SMTP_FROM=WireGuard-BlahBlah <[email protected]>

Here are the settings from GMAIL
smtp.gmail.com
Requires SSL: Yes
Requires TLS: Yes (if available)
Requires Authentication: Yes
Port for SSL: 465
Port for TLS/STARTTLS: 587

Also tried 587 with no avail. I use the same account to send outgoing smtp emails using other servies so should be all active.

protect page

I understand you're working to implement Authelia, but is there any simple way now to protect the page with even a simple password?

Suggestion , CLIENTS UI as Tabular list.

Hello,
I like your idea and project, thanks for the good work.

A suggestion towards the CLIENTS UI, i think it would be more useful to have the CLIENTS TAB as a Tabular list rather than each CLIENT being shown in Card form with the QR codealongside.

This is because the CLIENT and SERVER Tabs would normally be administered by an administrator not by the actual Client user. This way realestate screenspace can be better utilized , also not being the actual user there is no direct need for the QR code upfront on screen, a link should suffice (or a mouseover event).

just a thought, sadly i'm not a programmer to help out.

as said before good work ;)

Best Regards,
Aniston

Please share your setup

Would be nice if you can share your setup with a revers proxy in form of a docker-compose.yml ready to go for popular revers proxy web apps like.

  • nginx (i will add mine)
  • traefik
  • caddy
  • apache maybe

Thank you

Set server default for new client Allowed IPs

Would be great if we could set a default set of Allowed IPs for when we generate a new client config. In my case I have a few subnets that the users need access to and I am not routing for the entire web. Right now I need to add these subnets for each client manually. If we set this as a global pref it would streamline things considerably.

Great work!

wg0.conf not updating

Hi,

Great project - really like the flexibility of the UI which I haven't seen in any other gui's for wireguard yet. Super easy and the e-mail function works great!

I did discover something that may be due to how I setup my docker container, where the wg0.conf isn't updated but instead a wg0 file is being generated alongside.

In other words it seems like I need to the following to get new clients up and running:

mv wg0 wg0.conf
wg-quick down wg0; wg-quick up wg0

My questions is really if this is expected or if I should have done something else with the setup?

[feature request] Heimdall integration

Heimdall allows to group containers on a single web page for easy access.

It would be great to have the ability to also add wg-gen-web (and possibly show the number of configured clients, for instance)

Pagination + Search

Is it possible for dashboard using pagination + search? so admin can easily search vpn configuration.

Because for now, to load 500+ users configuration, it takes around 8-10s.. After scrolling, it will glitch and takes another 5s or more...

Thank you!

Add the possibility of more logging

It would be great if there was some more logging generated by the application.

Right now it is just the HTTP conversation that gets logged bus several what I belive to be key events are not:

  • start of the program (with some info about the version etc.)
  • client addition
  • emails sent (an error is logged)
  • changes in configuration

Enable/Disable Client Through List View

Before I just request on #38
I'm very helpful with that changes, and now... Is it possible to do "enable/disable client" on the list view?

For now, the only way to do "disable" on list view, are send request through proxy with the clientID.

Can I request feature "Enable/Disable Client through list view" ?

Thank you very much @vx3r !

PostUp, PostDown options

Would be nice to have an added textarea for additional server config options. Or expose PostUp and PostDown as optional fields for the server configuration.

Force Default Allowed IPs for client

His. Love this project!
But have a small request. Is it possible to have a checkbox or something similar to force the Default Allowed IPs for client to be applied to already existing peers?

Today the peers Allowed IPs is based the list of ips that exists during it's creation time. Would be nice to just add in the server part and all peers to be updated with this new setting.

SMTP sender configuration issue (SMTP_FROM)

The example for the SMTP configuration is

  - SMTP_HOST=smtp.gmail.com
  - SMTP_PORT=587
  - [email protected]
  - SMTP_PASSWORD="*************"
  - SMTP_FROM="Wg Gen Web <[email protected]>"

With this setup (and of course actual, correct data) I get the following error

time="2020-02-21T10:41:56Z" level=error msg="failed to send email to client" err="gomail: could not send email 1: gomail: invalid address \"\\\"Wg Gen Web <[email protected]>>\\\"\": mail: no angle-addr"

Changing SMTP_FROM to

- SMTP_FROM=Wg Gen Web <[email protected]>

(→ no quotes) fixes the problem and emails are sent out correctly.

Mobile version and fixed size artefacts

I am not sure this is fixable, and whether it should be but FYI the mobile version of the site is somehow impacted by some fixed-size artefacts:

image

This is not a show stopper as all the information is there (though, maybe there are issues with editing the IP ranges as the cross is eaten by the badge) - I am raising the point just in case there would be some more mobile dev.

The web version is truly fantastic, thanks for that.

(feat): database support

Would be nice to have support for database ( mysql, postgresql, sqlite ) for running multiple instances with same configuration.

cant login after vx3r/wg-gen-web:f90124af build

in browser console:

Error: "ApiService: TypeError: e is undefined"
    get api.service.js:14
App.vue:69
server report oauth2 is disabled, fake exchange auth.js:50:18
Updating authStatus from  to disabled App.vue:91
Updating authStatus from disabled to success App.vue:91

TLS support for sending email

Please add TLS support for SMTP, as most services mandate TLS connection.

d.TLSConfig = &tls.Config{InsecureSkipVerify: true}
(or use env for setup)

Thanks,

crashes due to multiple CIDRs input

how to reproduce from the UI

In the "Server interface addresses field type:
fd42:42:42::1/64
<enter>
10.8.0.1/24
<enter>
click "update server configuration"

a stack trace starting with fatal error: runtime: out of memory is dumped

with only a single ipv4 CIDR there is no problem

Email Error: ApiService: Error: Request failed with status code 500

Docker Log doesn't show anything helpful that I could see.

[GIN] 2020/08/26 - 21:31:30 | 401 |      58.876µs |    70.66.37.212 | GET      "/api/v1.0/client/7dd1c67a-daf6-4843-beda-fa19b5a1af28/email"
[GIN] 2020/08/26 - 21:31:30 | 200 |       104.6µs |    70.66.37.212 | GET      "/"
[GIN] 2020/08/26 - 21:31:30 | 200 |     596.848µs |    70.66.37.212 | GET      "/js/chunk-vendors.85a05be0.js"
[GIN] 2020/08/26 - 21:31:30 | 200 |     148.216µs |    70.66.37.212 | GET      "/js/app.68f6b65a.js"
[GIN] 2020/08/26 - 21:31:31 | 200 |     162.375µs |    70.66.37.212 | GET      "/css/Clients.b58a7d31.css"
[GIN] 2020/08/26 - 21:31:31 | 200 |      46.867µs |    70.66.37.212 | GET      "/api/v1.0/auth/oauth2_url"
[GIN] 2020/08/26 - 21:31:31 | 200 |     209.766µs |    70.66.37.212 | GET      "/js/Clients.34dc9b04.js"
[GIN] 2020/08/26 - 21:31:31 | 200 |      87.589µs |    70.66.37.212 | POST     "/api/v1.0/auth/oauth2_exchange"
[GIN] 2020/08/26 - 21:31:31 | 200 |     190.419µs |    70.66.37.212 | GET      "/js/Clients~Server.d72886b6.js"
[GIN] 2020/08/26 - 21:31:31 | 200 |     205.766µs |    70.66.37.212 | GET      "/js/Server.1d2011ab.js"
[GIN] 2020/08/26 - 21:31:31 | 200 |     228.314µs |    70.66.37.212 | GET      "/css/Clients~Server.4dad6da0.css"
[GIN] 2020/08/26 - 21:31:31 | 200 |      86.037µs |    70.66.37.212 | GET      "/api/v1.0/auth/user"
[GIN] 2020/08/26 - 21:31:31 | 200 |      57.274µs |    70.66.37.212 | GET      "/api/v1.0/server/version"
[GIN] 2020/08/26 - 21:31:31 | 200 |     198.925µs |    70.66.37.212 | GET      "/api/v1.0/server"
[GIN] 2020/08/26 - 21:31:31 | 200 |     235.897µs |    70.66.37.212 | GET      "/api/v1.0/client"
[GIN] 2020/08/26 - 21:31:31 | 200 |      98.538µs |    70.66.37.212 | GET      "/favicon.png"
[GIN] 2020/08/26 - 21:31:31 | 200 |      87.517µs |    70.66.37.212 | GET      "/api/v1.0/server/config"
[GIN] 2020/08/26 - 21:31:31 | 200 |      420.37µs |    70.66.37.212 | GET      "/api/v1.0/client/bb5c5b45-3ee4-4de5-9950-7ed2130bd967/config?qrcode=false"
[GIN] 2020/08/26 - 21:31:31 | 200 |     382.183µs |    70.66.37.212 | GET      "/api/v1.0/client/7dd1c67a-daf6-4843-beda-fa19b5a1af28/config?qrcode=false"
[GIN] 2020/08/26 - 21:31:31 | 200 |   12.729587ms |    70.66.37.212 | GET      "/api/v1.0/client/bb5c5b45-3ee4-4de5-9950-7ed2130bd967/config?qrcode=true"
[GIN] 2020/08/26 - 21:31:31 | 200 |   14.782369ms |    70.66.37.212 | GET      "/api/v1.0/client/7dd1c67a-daf6-4843-beda-fa19b5a1af28/config?qrcode=true"

Per client preshared key

Firstly, thanks for creating such a neat UI to manage WireGuard configs - this makes like so much easier!

I noticed that the generated client configs all use the same pre shared key. Given that the pre shared key is configured in each [Peer] section, would it be at all possible to have a separate pre shared key for each client? Essentially moving the pre shared key into each clients JSON file instead of storing a single one in the server's json file? It needn't be exposed in the UI.

Security concerns (general)

This project is really nice. This and other similar workflows pose a security concern that I'd like to be discussed here if possible: the generated configuration exhibited with a QR code or contained in a file to be moved to the peer contains everything is needed in order to be recognized as a valid peer.

Robust best practices would require that secrets be distributed following min two separate paths, and (even better) Wireguard was designed in a way that enables a peer to generate its own keys pair and send back to the server only the public part.

I understand this is a bit mor complicated as a process but I'm asking myself if I'm the only one concerned about this.

Migrate from Subspace

Any tips from us, when migrating from subspace to wg-gen-web?
I'm very interested...

Docker tags

For anyone who wants to use this "in production" it would be nice if you would provide actual release tags with the images published to hub.docker.com :)

Configuration parameters presentation

Configuration is composed of four parts:

  1. Server's Interface (the output of wg showconf wg0 - Address, ListenPort, keys )
  2. pre/post interface configuration (e.g. iptables)
  3. global clients peers conf suggested by server (DNS, MTU, Endpoint, PersistentKeepalive)
  4. per client configuration (allowedIPs, Address, peer keys)

I suggest that these 4 parts should be better identified on the UI.
For example

  1. first row left column box - "Server's Interface Configuration"
  2. first row right column box - pre/post interface configuration (used by wg-quick)
  3. middle row box - "client's global configuration" (DNS, MTU, Endpoint, PersistentKeepalive - also used by wg-quick)
  4. bottom boxes - client's specific configurations (as it is now).

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.