Code Monkey home page Code Monkey logo

netgear_exporter's People

Contributors

druggeri avatar ejsuncy avatar jcwimer 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

Watchers

 avatar  avatar  avatar  avatar

netgear_exporter's Issues

Add current bandwith usage

On the same page where I can view those same metrics, I am able to view the current throughput of the router. Is it possible you can add that too? Thanks.

traffic.calculatedelta does not work

Cannot make traffic.calculatedelta working either with parameter or with env var. Don't see any related code either. If it is not implemented yet, pls update readme.

Error while collecting traffic statistics: XML syntax error

# podman run -d --name my-netgear --restart=always --ip 10.88.0.9 \
    -e NETGEAR_EXPORTER_URL="https://192.168.100.1" \
    -e NETGEAR_EXPORTER_USERNAME="admin" \
    -e NETGEAR_EXPORTER_PASSWORD="XXXXXXXXXX" \
    -e NETGEAR_EXPORTER_INSECURE="true" \
    -e NETGEAR_EXPORTER_CLIENT_DEBUG="true" \
    docker.io/druggeri/netgear_exporter

# podman logs 6b7efe4c20ee
time="2023-07-27T20:12:22Z" level=info msg="Starting node_exporter 2.0.3" source="netgear_exporter.go:180"
2023/07/27 20:12:22 netgear_client.go: Constructing debug client
time="2023-07-27T20:12:22Z" level=info msg="Listening on :9192" source="netgear_exporter.go:230"
2023/07/27 20:12:52 netgear_client.go: full url (derived)='https://192.168.100.1/soap/server_sa/', data='<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<SOAP-ENV:Envelope
  xmlns:SOAPSDK1="http://www.w3.org/2001/XMLSchema"
  xmlns:SOAPSDK2="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:SOAPSDK3="http://schemas.xmlsoap.org/soap/encoding/"
  xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
  <SOAP-ENV:Header>
    <SessionID>A7D88AE69687E58D9A00</SessionID>
  </SOAP-ENV:Header>
  <SOAP-ENV:Body>
    <M1:GetAttachDevice xsi:nil="true" />
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>'
2023/07/27 20:12:52 netgear_client.go: full url (derived)='https://192.168.100.1/soap/server_sa/', data='<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<SOAP-ENV:Envelope
  xmlns:SOAPSDK1="http://www.w3.org/2001/XMLSchema"
  xmlns:SOAPSDK2="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:SOAPSDK3="http://schemas.xmlsoap.org/soap/encoding/"
  xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
  <SOAP-ENV:Header>
    <SessionID>A7D88AE69687E58D9A00</SessionID>
  </SOAP-ENV:Header>
  <SOAP-ENV:Body>
    <M1:GetSystemInfo xsi:nil="true" />
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>'
2023/07/27 20:12:52 netgear_client.go: full url (derived)='https://192.168.100.1/soap/server_sa/', data='<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<SOAP-ENV:Envelope
  xmlns:SOAPSDK1="http://www.w3.org/2001/XMLSchema"
  xmlns:SOAPSDK2="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:SOAPSDK3="http://schemas.xmlsoap.org/soap/encoding/"
  xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
  <SOAP-ENV:Header>
    <SessionID>A7D88AE69687E58D9A00</SessionID>
  </SOAP-ENV:Header>
  <SOAP-ENV:Body>
    <M1:GetTrafficMeterStatistics xmlns:M1="urn:NETGEAR-ROUTER:service:DeviceConfig:1">
    </M1:GetTrafficMeterStatistics>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>'
2023/07/27 20:12:52 netgear_client.go: Sending HTTP request to https://192.168.100.1/soap/server_sa/...
2023/07/27 20:12:52 netgear_client.go: Request headers:
2023/07/27 20:12:52 netgear_client.go:   Content-Length: 498
2023/07/27 20:12:52 netgear_client.go:   User-Agent: curl/7.59.0
2023/07/27 20:12:52 netgear_client.go:   Content-Type: text/xml;charset=utf-8
2023/07/27 20:12:52 netgear_client.go:   Soapaction: urn:NETGEAR-ROUTER:service:DeviceInfo:1#GetAttachDevice
2023/07/27 20:12:52 netgear_client.go:   Host: routerlogin.net
2023/07/27 20:12:52 netgear_client.go:   Cookie: UNSET
2023/07/27 20:12:52 netgear_client.go: BODY:
2023/07/27 20:12:52 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<SOAP-ENV:Envelope
  xmlns:SOAPSDK1="http://www.w3.org/2001/XMLSchema"
  xmlns:SOAPSDK2="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:SOAPSDK3="http://schemas.xmlsoap.org/soap/encoding/"
  xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
  <SOAP-ENV:Header>
    <SessionID>A7D88AE69687E58D9A00</SessionID>
  </SOAP-ENV:Header>
  <SOAP-ENV:Body>
    <M1:GetAttachDevice xsi:nil="true" />
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>
2023/07/27 20:12:52 netgear_client.go: Sending HTTP request to https://192.168.100.1/soap/server_sa/...
2023/07/27 20:12:52 netgear_client.go: Request headers:
2023/07/27 20:12:52 netgear_client.go:   User-Agent: curl/7.59.0
2023/07/27 20:12:52 netgear_client.go:   Content-Type: text/xml;charset=utf-8
2023/07/27 20:12:52 netgear_client.go:   Soapaction: urn:NETGEAR-ROUTER:service:DeviceInfo:1#GetSystemInfo
2023/07/27 20:12:52 netgear_client.go: Sending HTTP request to https://192.168.100.1/soap/server_sa/...
2023/07/27 20:12:52 netgear_client.go: Request headers:
2023/07/27 20:12:52 netgear_client.go:   User-Agent: curl/7.59.0
2023/07/27 20:12:52 netgear_client.go:   Content-Type: text/xml;charset=utf-8
2023/07/27 20:12:52 netgear_client.go:   Soapaction: urn:NETGEAR-ROUTER:service:DeviceConfig:1#GetTrafficMeterStatistics
2023/07/27 20:12:52 netgear_client.go:   Host: routerlogin.net
2023/07/27 20:12:52 netgear_client.go:   Cookie: UNSET
2023/07/27 20:12:52 netgear_client.go:   Content-Length: 580
2023/07/27 20:12:52 netgear_client.go: BODY:
2023/07/27 20:12:52 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<SOAP-ENV:Envelope
  xmlns:SOAPSDK1="http://www.w3.org/2001/XMLSchema"
  xmlns:SOAPSDK2="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:SOAPSDK3="http://schemas.xmlsoap.org/soap/encoding/"
  xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
  <SOAP-ENV:Header>
    <SessionID>A7D88AE69687E58D9A00</SessionID>
  </SOAP-ENV:Header>
  <SOAP-ENV:Body>
    <M1:GetTrafficMeterStatistics xmlns:M1="urn:NETGEAR-ROUTER:service:DeviceConfig:1">
    </M1:GetTrafficMeterStatistics>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>
2023/07/27 20:12:52 netgear_client.go:   Host: routerlogin.net
2023/07/27 20:12:52 netgear_client.go:   Cookie: UNSET
2023/07/27 20:12:52 netgear_client.go:   Content-Length: 496
2023/07/27 20:12:52 netgear_client.go: BODY:
2023/07/27 20:12:52 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<SOAP-ENV:Envelope
  xmlns:SOAPSDK1="http://www.w3.org/2001/XMLSchema"
  xmlns:SOAPSDK2="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:SOAPSDK3="http://schemas.xmlsoap.org/soap/encoding/"
  xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
  <SOAP-ENV:Header>
    <SessionID>A7D88AE69687E58D9A00</SessionID>
  </SOAP-ENV:Header>
  <SOAP-ENV:Body>
    <M1:GetSystemInfo xsi:nil="true" />
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>
2023/07/27 20:12:52 netgear_client.go: Response code: 401
2023/07/27 20:12:52 netgear_client.go: Response headers:
2023/07/27 20:12:52 netgear_client.go:   Www-Authenticate: Basic realm="NETGEAR R6700v3"
2023/07/27 20:12:52 netgear_client.go:   X-Frame-Options: SAMEORIGIN
2023/07/27 20:12:52 netgear_client.go:   Set-Cookie: XSRF_TOKEN=1222440606; Path=/
2023/07/27 20:12:52 netgear_client.go:   Content-Type: text/html
time="2023-07-27T20:12:52Z" level=error msg="Error while collecting client statistics: XML syntax error on line 4: element <meta> closed by </head>" source="clients_collector.go:122"
2023/07/27 20:12:52 netgear_client.go: Response code: 401
2023/07/27 20:12:52 netgear_client.go: Response headers:
2023/07/27 20:12:52 netgear_client.go:   Www-Authenticate: Basic realm="NETGEAR R6700v3"
2023/07/27 20:12:52 netgear_client.go:   X-Frame-Options: SAMEORIGIN
2023/07/27 20:12:52 netgear_client.go:   Set-Cookie: XSRF_TOKEN=1222440606; Path=/
2023/07/27 20:12:52 netgear_client.go:   Content-Type: text/html
time="2023-07-27T20:12:52Z" level=error msg="Error while collecting system info: XML syntax error on line 4: element <meta> closed by </head>" source="system_info_collector.go:110"
2023/07/27 20:12:52 netgear_client.go: Response code: 401
2023/07/27 20:12:52 netgear_client.go: Response headers:
2023/07/27 20:12:52 netgear_client.go:   Www-Authenticate: Basic realm="NETGEAR R6700v3"
2023/07/27 20:12:52 netgear_client.go:   X-Frame-Options: SAMEORIGIN
2023/07/27 20:12:52 netgear_client.go:   Set-Cookie: XSRF_TOKEN=1222440606; Path=/
2023/07/27 20:12:52 netgear_client.go:   Content-Type: text/html
time="2023-07-27T20:12:52Z" level=error msg="Error while collecting traffic statistics: XML syntax error on line 4: element <meta> closed by </head>" source="traffic_collector.go:126"


XML syntax errors

Seeing these errors when using the exporter. Maybe netgear updated their xml or something?

I am using a Nighthawk R7000 with firmware version: V1.0.9.64_10.2.64

I didn't see any additional info while using log.level=debug

ERRO[0364] Error while collecting system info: XML syntax error on line 4: element <meta> closed by </head>  source="system_info_collector.go:110"
ERRO[0364] Error while collecting traffic statistics: XML syntax error on line 4: element <meta> closed by </head>  source="traffic_collector.go:126"
ERRO[0364] Error while collecting client statistics: XML syntax error on line 4: element <meta> closed by </head>  source="clients_collector.go:122"

Certificate errors when starting with INSECURE=true

When starting a container using docker-compose the logs indicate an issue with an x509 certificate despite the URL being a LAN address and INSECURE being set to "true".

docker-compose.yml:

version: "3"
services:
  netgear_exporter:
    image: druggeri/netgear_exporter:latest
    container_name: netgear_exporter
    environment:
      - URL=${URL}
      - USERNAME=${USERNAME}
      - PASSWORD=${PASSWORD}
      - NETGEAR_EXPORTER_PASSWORD=${PASSWORD}
      - WEB_AUTH_USERNAME=${WEB_AUTH_USERNAME}
      - WEB_AUTH_PASSWORD=${WEB_AUTH_PASSWORD}
      - INSECURE=true
      - CLIENTDEBUG
      - METRICS_NAMESPACE=netgear
      - TIMEOUT=2
    ports:
      - 9192:9192
    restart: unless-stopped

.env.example:

# Container specifics
URL=[url]
USERNAME=[username]
PASSWORD=[password]
WEB_AUTH_USERNAME=[username]
WEB_AUTH_PASSWORD=[password]
NETGEAR_EXPORTER_PASSWORD=[password]

log file tail:

$ docker-compose --file /srv/netgear_exporter/docker-compose.yml logs
Attaching to netgear_exporter
netgear_exporter    | time="2022-10-18T16:34:19Z" level=info msg="Starting node_exporter 2.0.3" source="netgear_exporter.go:180"
netgear_exporter    | time="2022-10-18T16:34:19Z" level=info msg="Listening on :9192" source="netgear_exporter.go:230"
netgear_exporter    | time="2022-10-18T16:34:21Z" level=error msg="Error while collecting system info: Post \"https://www.routerlogin.com/soap/server_sa/\": x509: certificate is not valid for any names, but wanted to match www.routerlogin.com" source="system_info_collector.go:110"
netgear_exporter    | time="2022-10-18T16:34:21Z" level=error msg="Error while collecting client statistics: Post \"https://www.routerlogin.com/soap/server_sa/\": x509: certificate is not valid for any names, but wanted to match www.routerlogin.com" source="clients_collector.go:122"
netgear_exporter    | time="2022-10-18T16:34:21Z" level=error msg="Error while collecting traffic statistics: Post \"https://www.routerlogin.com/soap/server_sa/\": x509: certificate is not valid for any names, but wanted to match www.routerlogin.com" source="traffic_collector.go:126"

The container seems to be trying to connect to www.routerlogin.com rather than my LAN address. Here is a sanitised version of the loaded configuration:

$ docker-compose --file /srv/netgear_exporter/docker-compose.yml config
services:
  netgear_exporter:
    container_name: netgear_exporter
    environment:
      CLIENTDEBUG: null
      INSECURE: "true"
      METRICS_NAMESPACE: netgear
      NETGEAR_EXPORTER_PASSWORD: [password]
      PASSWORD: [password]
      TIMEOUT: '2'
      URL: http://192.168.1.1/
      USERNAME: [username]
      WEB_AUTH_PASSWORD: [username]
      WEB_AUTH_USERNAME: [username]
    image: druggeri/netgear_exporter:latest
    ports:
    - published: 9192
      target: 9192
    restart: unless-stopped
version: '3'

Any ideas what is happening?

Client names for wireless strength/speed

I'm not sure how to create a Grafana chart showing client names next to their respective wireless speed/strength, because it's not present in netgear_client_wireless_speed or netgear_client_wireless_strength but present in netgear_client_info.

Can you help me here? Thanks.

Inconsistent Environment variables around password fields

When using docker-compose the password fields are inconsistent. Using the following compose file results in an error:

version: "3"
services:
  netgear_exporter:
    image: druggeri/netgear_exporter:latest
    container_name: netgear_exporter
    environment:
      - URL=${URL}
      - USERNAME=${USERNAME}
      - PASSWORD=${PASSWORD}
      - WEB_AUTH_USERNAME=${WEB_AUTH_USERNAME}
      - WEB_AUTH_PASSWORD=${WEB_AUTH_PASSWORD}
      - INSECURE=true
      - CLIENTDEBUG
      - METRICS_NAMESPACE=netgear
      - TIMEOUT=2
    ports:
      - 9192:9192
    restart: unless-stopped

The error is around the environment variable:

$ docker-compose --file /srv/netgear_exporter/docker-compose.yml logs
Attaching to netgear_exporter
netgear_exporter    | ERROR: The password for the SOAP API must be set in the environment variable NETGEAR_EXPORTER_PASSWORD
netgear_exporter    | ERROR: The password for the SOAP API must be set in the environment variable NETGEAR_EXPORTER_PASSWORD
netgear_exporter    | ERROR: The password for the SOAP API must be set in the environment variable NETGEAR_EXPORTER_PASSWORD
netgear_exporter    | ERROR: The password for the SOAP API must be set in the environment variable NETGEAR_EXPORTER_PASSWORD
netgear_exporter    | ERROR: The password for the SOAP API must be set in the environment variable NETGEAR_EXPORTER_PASSWORD

Are all environment variables expected to be prefixed with NETGEAR_EXPORTER_ ?

Enhancement request for -h or readme

Hello, Would it be possible to add more details into the help command or readme?

example would be what need to be set to run the exporter. (maybe its my understanding but feels like it could use more details)
export NETGEAR_EXPORTER_URL="1.1.1.1"
export NETGEAR_EXPORTER_WEB_LISTEN_ADDRESS="localhost:9192"
export NETGEAR_EXPORTER_USERNAME="admin"
export NETGEAR_EXPORTER_PASSWORD=cat /home/netgear_exporter/.netgear_exporter_password
export NETGEAR_EXPORTER_INSECURE="true"
export NETGEAR_EXPORTER_TIMEOUT=2
export NETGEAR_EXPORTER_FILTER_COLLECTORS=Client,SystemInfo,Traffic
export NETGEAR_EXPORTER_METRICS_NAMESPACE=netgear

Also maybe a little detail around making it into a services.

netgear_exporter.service
[Unit]
Description=NETGEAR Exporter

[Service]
Environment="NETGEAR_EXPORTER_URL="1.1.1.1""
Environment="NETGEAR_EXPORTER_WEB_LISTEN_ADDRESS="localhost:9192""
Environment="NETGEAR_EXPORTER_USERNAME="admin""
Environment="NETGEAR_EXPORTER_PASSWORD=cat /home/netgear_exporter/.netgear_exporter_password"
Environment="NETGEAR_EXPORTER_INSECURE="true""
Environment="NETGEAR_EXPORTER_TIMEOUT=2"
Environment="NETGEAR_EXPORTER_FILTER_COLLECTORS=Client,SystemInfo,Traffic"
Environment="NETGEAR_EXPORTER_METRICS_NAMESPACE=netgear"
ExecStart=/usr/local/bin/netgear_exporter --url="$NETGEAR_EXPORTER_URL" --username="$NETGEAR_EXPORTER_USERNAME" --insecure --log.level="debug" --web.listen-address=":9192" --web.telemetry-path="/metrics"

[Install]
WantedBy=multi-user.target

Multiple netgear devices?

Isn't it possible to target multiple netgear devices with only one running instance of this exporter?

XML syntax error

When running the exporter by hand getting an odd exception when the exporter is running.

/usr/local/bin/netgear_exporter --url="$NETGEAR_EXPORTER_URL" --username="$NETGEAR_EXPORTER_USERNAME" --insecure --log.level="debug" --web.listen-address=":9192" --web.telemetry-path="/metrics"
INFO[0000] Starting node_exporter testing source="netgear_exporter.go:180"
INFO[0000] Listening on :9192 source="netgear_exporter.go:230"
ERRO[0003] Error while collecting traffic statistics: XML syntax error on line 4: element closed by source="traffic_collector.go:126"
ERRO[0003] Error while collecting client statistics: XML syntax error on line 4: element closed by source="clients_collector.go:122"
ERRO[0003] Error while collecting system info: XML syntax error on line 4: element closed by source="system_info_collector.go:110"
ERRO[0069] Error while collecting traffic statistics: XML syntax error on line 4: element closed by source="traffic_collector.go:126"
ERRO[0069] Error while collecting client statistics: XML syntax error on line 4: element closed by source="clients_collector.go:122"
ERRO[0069] Error while collecting system info: XML syntax error on line 4: element closed by source="system_info_collector.go:110"
ERRO[0070] Error while collecting client statistics: XML syntax error on line 4: element closed by source="clients_collector.go:122"
ERRO[0071] Error while collecting system info: XML syntax error on line 4: element closed by source="system_info_collector.go:110"
ERRO[0071] Error while collecting traffic statistics: XML syntax error on line 4: element closed by source="traffic_collector.go:126"
ERRO[0072] Error while collecting system info: XML syntax error on line 4: element closed by source="system_info_collector.go:110"
ERRO[0072] Error while collecting traffic statistics: XML syntax error on line 4: element closed by source="traffic_collector.go:126"
ERRO[0072] Error while collecting client statistics: XML syntax error on line 4: element closed by source="clients_collector.go:122"

The exporter is up but not sure if this is good or bad.

Needs ability to set charset

Hey there, depending on the device local you're looking at, you may need to allow … slightly ancient charsets, apparently:

ERRO[0010] Error while collecting traffic statistics: xml: encoding "iso-8859-1" declared but Decoder.CharsetReader is nil  source="traffic_collector.go:126"
ERRO[0010] Error while collecting client statistics: xml: encoding "iso-8859-1" declared but Decoder.CharsetReader is nil  source="clients_collector.go:122"
ERRO[0010] Error while collecting system info: xml: encoding "iso-8859-1" declared but Decoder.CharsetReader is nil  source="system_info_collector.go:110"

Yes, that's latin1… not quite sure why Netgear's still using it, but it's there alright.

Rate limits

Do you know if there are rate limits on the router's soap server? I have been pinging it every 15 sec to be able to do some bandwidth calculations and there are "holes" in the data where it times out, I have the timeout set to 5 seconds. Here is the errors that I get:

level=error msg="Error while collecting client statistics: Post \"http://10.0.1.1/soap/server_sa/\": context deadline exceeded (Client.Timeout exceeded while awaiting headers)" source="clients_collector.go:122"

and

level=error msg="Error while collecting system info: The netgear_client is not logged in!" source="system_info_collector.go:110"

the Netgear_client login error does not appear frequently, about 1 time for every 10 of the first error.

XML parsing error on Netgear X6S Router (R8000P)

Each time I call the /metrics it prints some logs:

INFO[0000] Starting node_exporter (version=, branch=, revision=)  source="netgear_exporter.go:174"
INFO[0000] Build context (go=go1.10.4, user=, date=)     source="netgear_exporter.go:175"
INFO[0000] Listening on :9192                            source="netgear_exporter.go:224"

ERRO[0003] Error while collecting traffic statistics: XML syntax error on line 4: element <meta> closed by </head>  source="traffic_collector.go:126"
ERRO[0003] Error while collecting system info: XML syntax error on line 4: element <meta> closed by </head>  source="system_info_collector.go:110"
ERRO[0003] Error while collecting client statistics: XML syntax error on line 4: element <meta> closed by </head>  source="clients_collector.go:122"
ERRO[0005] Error while collecting client statistics: XML syntax error on line 4: element <meta> closed by </head>  source="clients_collector.go:122"

Can we print out the whole response to debug what the problem is?

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.