Giter Site home page Giter Site logo

switchport-web-dashboard's Introduction

switchport-web-dashboard's People

Contributors

0x2142 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

Watchers

 avatar  avatar  avatar  avatar  avatar

switchport-web-dashboard's Issues

Issue with Database #sqlite3.OperationalError: near "ON": syntax error

Hello Mate,

You are doing great job and I am learning so much from your posts time to time.

now coming to issues I am facing, I have modified codes per my requirement which is very simple to add count of ports with traffic[in_octet] so added 2 more key in below dict.

    interfaceStats = {
        "total_port": 0,
        "up_port": 0,
        "down_port": 0,
        "disabled_port": 0,
        "in_octet_zero": 0, 
        "in_octet_traffic": 0,               
        "intop10m": 0,
        "intop100m": 0,
        "intop1g": 0,
        "intop10g": 0,
        "intop25g": 0,
        "intop40g": 0,
        "intop100g": 0,
        "intmedcop": 0,
        "intmedsfp": 0,
        "intmedvirtual": 0,
    }

this is under file data_collector.py.

attaching modified code below.

import os
import yaml
from scrapli.driver.core import IOSXEDriver, NXOSDriver, IOSXRDriver
import switchdb

def loadDevices():
    """
    Load device inventory from config.yml
    """
    print("Loading devices from config file...")
    with open("config.yml", "r") as config:
        devicelist = yaml.full_load(config)
    return devicelist["Devices"]


def connectToDevice(deviceconfig):
    """
    Parse device config data & open SSH connection
    """
    print("Loading device configuration...")
    device = {}
    device["host"] = deviceconfig["address"]
    device["auth_username"] = deviceconfig["username"]
    device["auth_password"] = deviceconfig["password"]
    device["auth_strict_key"] = False
    device["timeout_socket"] = 10
    device["timeout_ops"] = 10
    try:
        device["port"] = deviceconfig["port"]
    except KeyError:
        pass
    if deviceconfig["type"] == "ios-xe":
        conn = IOSXEDriver(**device)
    elif deviceconfig["type"] == "nx-os":
        conn = NXOSDriver(**device)
    try:
        print(f"Attempting connection to {device['host']}")
        conn.open()
        print(f"Successfully connected to {device['host']}")
    except Exception as e:
        print(f"Failed connection to {device['host']}")
        print("Error message is: %s" % e)
        return None
    return conn


def getInterfaceInfo(device):
    """
    Issue 'Show Interfaces' command to device
    Process data & populate dict with interface status
    """
    # Send command to device
    if type(device) == IOSXEDriver:
        resp = device.send_command("show interfaces")
    if type(device) == NXOSDriver:
        resp = device.send_command("show interface")
    # Save a copy of the raw output
    save_raw_output(resp)
    # Parse raw CLI using Genie
    intdata = resp.genie_parse_output()
    interfaceStats = {
        "total_port": 0,
        "up_port": 0,
        "down_port": 0,
        "disabled_port": 0,
        "in_octet_zero": 0, 
        "in_octet_traffic": 0,               
        "intop10m": 0,
        "intop100m": 0,
        "intop1g": 0,
        "intop10g": 0,
        "intop25g": 0,
        "intop40g": 0,
        "intop100g": 0,
        "intmedcop": 0,
        "intmedsfp": 0,
        "intmedvirtual": 0,
    }
    # Init dict for detailed interface operational stat collection
    intDetailed = {}
    # Process each interface
    for iface in intdata:
        # Skip VLAN / PortChannel Interfaces
        if "Ethernet" not in iface:
            print(f"found non-ethernet interface: {iface}")
            continue
        if "GigabitEthernet0/0" in iface:
            print(f"found management interface: {iface}")
            continue
        print(f"Working on interface {iface}")
        # Collect detailed interface stats (name, oper status, description, MAC)
        intDetailed[iface] = {}
        intDetailed[iface]["oper_status"] = intdata[iface]["oper_status"]
        try:
            intDetailed[iface]["description"] = intdata[iface]["description"]
        except:
            intDetailed[iface]["description"] = "N/A"
        intDetailed[iface]["phys_addr"] = intdata[iface]["phys_address"]
        intDetailed[iface]["oper_speed"] = intdata[iface]["bandwidth"]
        try:
            intDetailed[iface]["in_octet"] = intdata[iface]["counters"]["in_octets"]
        except KeyError:
            pass                
        # Count all Ethernet interfaces
        interfaceStats["total_port"] += 1
        # Count admin-down interfaces
        if not intdata[iface]["enabled"]:
            interfaceStats["disabled_port"] += 1
        # Count 'not connected' interfaces
        elif intdata[iface]["enabled"] and intdata[iface]["oper_status"] == "down":
            interfaceStats["down_port"] += 1
        # Count up / connected interfaces - Then collect current speeds
        elif intdata[iface]["enabled"] and intdata[iface]["oper_status"] == "up":
            interfaceStats["up_port"] += 1
            speed = intdata[iface]["bandwidth"]
            if speed == 10000:
                interfaceStats["intop10m"] += 1
            if speed == 100000:
                interfaceStats["intop100m"] += 1
            if speed == 1000000:
                interfaceStats["intop1g"] += 1
            if speed == 10000000:
                interfaceStats["intop10g"] += 1
            if speed == 25000000:
                interfaceStats["intop25g"] += 1
            if speed == 40000000:
                interfaceStats["intop40g"] += 1
            if speed == 100000000:
                interfaceStats["intop100g"] += 1
        # Count number of interfaces by media type
        try:
            media = intdata[iface]["media_type"]
            if "1000BaseTX" in media:
                interfaceStats["intmedcop"] += 1
            elif "Virtual" in media:
                interfaceStats["intmedvirtual"] += 1
            else:
                interfaceStats["intmedsfp"] += 1
        except KeyError:
            interfaceStats["intmedsfp"] += 1
        # In_octet == 0  
        try:
            if intdata[iface]['counters']['in_octets'] <= 0:
                interfaceStats["in_octet_zero"] += 1 
        except KeyError:
            pass
        try:
            if intdata[iface]['counters']['in_octets'] > 0:
                interfaceStats["in_octet_traffic"] += 1 
        except KeyError:
            pass
    # When complete - return int stats list
    return interfaceStats, intDetailed


def save_raw_output(data):
    """
    Creates a local working directory where all raw CLI
    output is stored.
    """
    # Create local directory to store raw output
    if not os.path.exists("raw_output"):
        os.makedirs("raw_output")
    # Dump port information to file
    with open(f"raw_output/{system_serial}.txt", "w") as a:
        a.write(data.result)


def getSystemInfoXE(device):
    """
     -- FOR IOS-XE DEVICES --
    Issue 'Show Version' command to device
    Return serial number, model, current software version
    """
    resp = device.send_command("show version")
    parsed = resp.genie_parse_output()
    sysinfo = {}
    sysinfo["serial"] = parsed["version"]["chassis_sn"]
    sysinfo["model"] = parsed["version"]["chassis"]
    sysinfo["sw_ver"] = parsed["version"]["version"]
    global system_serial
    system_serial = sysinfo["serial"]
    return sysinfo


def getSystemInfoNX(device):
    """
     -- FOR NX-OS DEVICES --
    Issue 'Show Version' command to device
    Return serial number, model, current software version
    """
    resp = device.send_command("show version")
    parsed = resp.genie_parse_output()
    sysinfo = {}
    sysinfo["serial"] = parsed["platform"]["hardware"]["processor_board_id"]
    sysinfo["model"] = parsed["platform"]["hardware"]["model"]
    sysinfo["sw_ver"] = parsed["platform"]["software"]["system_version"]
    global system_serial
    system_serial = sysinfo["serial"]
    return sysinfo


def addDeviceToDB(devicelist):
    """
    Update DB entries for each switch from the config file
    """
    print("Opening DB connection...")
    swDB = switchdb.DB()
    # Get a list of current switches in the database
    # Compare between new config file - see what should be added/removed
    curswitches = swDB.getAllSummary()
    currentSwitches = [row[3] for row in curswitches]
    newSwitches = [devicelist[switch]["address"] for switch in devicelist]
    swRemove = set(currentSwitches).difference(newSwitches)
    swAdd = set(newSwitches).difference(currentSwitches)
    # If switches to remove, purge from the database
    if len(swRemove) > 0:
        print(f"Found {len(swRemove)} switches no longer in config file")
        for switchIP in swRemove:
            print(f"Removing switch ({switchIP}) from DB...")
            swDB.deleteSwitch(switchIP)

    print("Adding devices to DB...")
    for switch in devicelist:
        switchIP = devicelist[switch]["address"]
        if switchIP in swAdd:
            print(f"Adding switch ({switch} / {switchIP}) to DB...")
            swDB.addSwitch(str(switch), str(switchIP))
        else:
            print(f"Switch ({switch} / {switchIP}) already in DB. Skipping...")
    swDB.close()


def updateDB(device, ip, sysinfo, portinfo, detailedinfo):
    """
    Insert new system & port information
    into the database
    """
    swDB = switchdb.DB()
    print(f"Updating system info for {device} in DB...")
    swDB.updateSysInfo(device, ip, sysinfo)
    print(f"Updating port info for {device} in DB...")
    swDB.updatePorts(device, ip, portinfo)
    print(f"Updating detailed port info for {device} in DB...")
    swDB.updateInterfaceDetails(device, ip, sysinfo, detailedinfo)
    swDB.close()


def updateLastRun():
    """
    Call to DB - update last run time
    """
    swDB = switchdb.DB()
    print("Updating last run time in DB...")
    swDB.updateLastRun()
    swDB.close()


def updateCheckStatus(device, ip, status):
    """
    Update the last_check database field,
    which indicates if the check passed or failed
    """
    swDB = switchdb.DB()
    print(f"Updating check status for {device} to {status}")
    swDB.updateStatus(device, ip, status)
    swDB.close()


def run():
    """
    Primay function to manage device data collection
    """
    # Load all of our devices from config, then add to DB
    devicelist = loadDevices()
    addDeviceToDB(devicelist)
    # Iterate through each device for processing
    for device in devicelist:
        dev = device
        ip = devicelist[device]["address"]
        # Open device connection
        devcon = connectToDevice(devicelist[device])
        if devcon:
            try:
                # Query device for system & port info
                if type(devcon) == IOSXEDriver:
                    sysinfo = getSystemInfoXE(devcon)
                if type(devcon) == NXOSDriver:
                    sysinfo = getSystemInfoNX(devcon)
                portinfo, detailedinfo = getInterfaceInfo(devcon)
            except Exception as e:
                print(f"ERROR: {e}")
                updateCheckStatus(device, ip, False)
                continue
            # Update database with new info
            updateDB(dev, ip, sysinfo, portinfo, detailedinfo)
            # Update database with interface detail info
            # Update if check succeeeded
            updateCheckStatus(dev, ip, True)
        else:
            # Update DB if last check failed
            updateCheckStatus(device, ip, False)
    # Finally, update the last-run time!
    updateLastRun()


if __name__ == "__main__":
    run()

And further below is Database file, I had edited codes in DB files but seems I can't get it fixed.

import sqlite3
from datetime import datetime
from sqlite3 import Error


class DB:
    def __init__(self):
        self.openDB()
        self.createDB()
        self.initLastUpdate()

    def openDB(self):
        """
        Open SQLlite DB
        """
        self.conn = None
        try:
            self.conn = sqlite3.connect("./sw-util.db")
        except Error as e:
            print(e)

    def createDB(self):
        """
        Create new table to contain switch info & port utilization data
        """
        sw_info_table = """ CREATE TABLE IF NOT EXISTS switches (
            name text NOT NULL,
            serial text DEFAULT "Not Polled Yet",
            model text DEFAULT "N/A",
            sw_ver text DEFAULT "N/A",
            mgmt_ip text NOT NULL PRIMARY KEY,
            last_check boolean DEFAULT False,
            total_port integer DEFAULT 0,
            up_port integer DEFAULT 0,
            down_port integer DEFAULT 0,
            disabled_port integer DEFAULT 0,
            in_octet_zero integer DEFAULT 0,
            in_octet_traffic integer DEFAULT 0,
            intop10m integer DEFAULT 0,
            intop100m integer DEFAULT 0,
            intop1g integer DEFAULT 0,
            intop10g integer DEFAULT 0,
            intop25g integer DEFAULT 0,
            intop40g integer DEFAULT 0,
            intop100g integer DEFAULT 0,
            intmedcop integer DEFAULT 0,
            intmedsfp integer DEFAULT 0,
            intmedvirt integer DEFAULT 0
        ); """
        last_update_table = """ CREATE TABLE IF NOT EXISTS last_update (
            id integer NOT NULL PRIMARY KEY,
            lastrun text NOT NULL
        ); """
        interface_detail_table = """ CREATE TABLE IF NOT EXISTS interface_detailed (
            sw_name text NOT NULL,
            mgmt_ip text NOT NULL,
            int_name text NOT NULL,
            oper_status text NOT NULL,
            description text DEFAULT "N/A",
            phys_address text NOT NULL PRIMARY KEY,
            serial text NOT NULL,
            oper_speed text NOT NULL,
            in_octet text NOT NULL
        ); """
        cur = self.conn.cursor()
        cur.execute(sw_info_table)
        cur.execute(last_update_table)
        cur.execute(interface_detail_table)

    def addSwitch(self, name, mgmt_ip):
        """
        Insert new switch into DB
        """
        sql = """ INSERT INTO switches(name,mgmt_ip) values(?,?); """
        cur = self.conn.cursor()
        try:
            cur.execute(sql, (name, mgmt_ip))
            self.conn.commit()
        except sqlite3.IntegrityError:
            print(f"Switch {name} with IP: {mgmt_ip} already exists in DB.")

    def updateSysInfo(self, name, mgmt_ip, sysinfo):
        """
        Update switch system info:
        Model number, software version, and serial number
        """
        sql = """ UPDATE switches
                  SET serial = ?,
                  model = ?,
                  sw_ver = ?
                  WHERE name = ?
                  AND mgmt_ip = ?;
        """
        cur = self.conn.cursor()
        cur.execute(
            sql, (sysinfo["serial"], sysinfo["model"], sysinfo["sw_ver"], name, mgmt_ip)
        )
        self.conn.commit()
        return

    def updatePorts(self, name, mgmt_ip, portinfo):
        """
        Update port count information
        """
        sql = """ UPDATE switches
                  SET
                  total_port = ?,
                  up_port = ?,
                  down_port = ?,
                  disabled_port = ?,
                  in_octet_zero = ?,
                  in_octet_traffic = ?,
                  intop10m = ?,
                  intop100m = ?,
                  intop1g = ?,
                  intop10g = ?,
                  intop25g = ?,
                  intop40g = ?,
                  intop100g = ?,
                  intmedcop = ?,
                  intmedsfp = ?,
                  intmedvirt = ?
                  WHERE name = ?
                  AND mgmt_ip = ?;
        """
        cur = self.conn.cursor()
        cur.execute(
            sql,
            (
                portinfo["total_port"],
                portinfo["up_port"],
                portinfo["down_port"],
                portinfo["disabled_port"],
                portinfo["in_octet_zero"],
                portinfo["in_octet_traffic"],
                portinfo["intop10m"],
                portinfo["intop100m"],
                portinfo["intop1g"],
                portinfo["intop10g"],
                portinfo["intop25g"],
                portinfo["intop40g"],
                portinfo["intop100g"],
                portinfo["intmedcop"],
                portinfo["intmedsfp"],
                portinfo["intmedvirtual"],
                name,
                mgmt_ip,
            ),
        )
        self.conn.commit()
        return

    def updateInterfaceDetails(self, name, mgmt_ip, sysinfo, portdetails):
        """
        Update interface detailed status per switch:
        Interface name, operational status, description, and physical address
        """
        sql = """ INSERT INTO interface_detailed(
                    int_name,
                    oper_status,
                    description,
                    phys_address,
                    oper_speed,
                    in_octet,
                    sw_name, 
                    mgmt_ip,
                    serial
                  )
                  VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)
                  ON CONFLICT(phys_address) DO UPDATE
                  SET int_name = ?,
                  oper_status = ?,
                  description = ?,
                  phys_address = ?,
                  oper_speed = ?,
                  in_octet = ?,
                  serial = ?
                  WHERE sw_name = ?
                  AND mgmt_ip = ?;
        """
        cur = self.conn.cursor()
        for interface in portdetails:
            cur.execute(
                sql,
                (
                    interface,
                    portdetails[interface]["oper_status"],
                    portdetails[interface]["description"],
                    portdetails[interface]["phys_addr"],
                    portdetails[interface]["oper_speed"],
                    portdetails[interface]["in_octet"],
                    name,
                    mgmt_ip,
                    sysinfo["serial"],
                    interface,
                    portdetails[interface]["oper_status"],
                    portdetails[interface]["description"],
                    portdetails[interface]["phys_addr"],
                    portdetails[interface]["oper_speed"],
                    portdetails[interface]["in_octet"],
                    sysinfo["serial"],
                    name,
                    mgmt_ip,
                ),
            )
            self.conn.commit()
        return

    def getSwitch(self, name, mgmt_ip):
        """
        Retrieve switch information
        """
        sql = """ SELECT * FROM switches
                  WHERE name = ? AND mgmt_ip = ?; """
        cur = self.conn.cursor()
        cur.execute(sql, (name, mgmt_ip))
        result = cur.fetchall()
        return result

    def deleteSwitch(self, mgmt_ip):
        """
        Remove switch from database
        """
        sql = """ DELETE FROM switches
                  WHERE mgmt_ip = ?; """
        cur = self.conn.cursor()
        cur.execute(sql, [mgmt_ip])
        result = cur.fetchall()
        return result

    def getNetworkWideStats(self):
        """
        Retrieve network-wide port count information
        """
        sql = """ SELECT model, sw_ver, total_port, up_port, down_port,
                  disabled_port, in_octet_zero, in_octet_traffic, intop10m, intop100m, intop1g, intop10g,
                  intop25g, intop40g, intop100g,intmedcop, intmedsfp,
                  intmedvirt FROM switches; """
        cur = self.conn.cursor()
        cur.execute(sql)
        result = cur.fetchall()
        return result

    def getAllSummary(self):
        """
        Retrieve info from ALL switches in DB.
        """
        sql = """ SELECT name, serial, sw_ver, mgmt_ip, last_check,
                  total_port, up_port, down_port, disabled_port
                  FROM switches; """
        cur = self.conn.cursor()
        cur.execute(sql)
        result = cur.fetchall()
        return result

    def getSwitchDetail(self, serial):
        """
        Retrieve info from ALL switches in DB.
        """
        sql = """ SELECT * FROM switches WHERE serial = ?; """
        cur = self.conn.cursor()
        cur.execute(sql, [serial])
        result = cur.fetchall()
        return result

    def getInterfaceDetail(self, serial):
        """
        Retrieve interface detailed info
        """
        sql = """ SELECT int_name, description, phys_address, oper_status,
                  oper_speed, in_octet
                  FROM interface_detailed WHERE serial = ? """
        cur = self.conn.cursor()
        cur.execute(sql, [serial])
        result = cur.fetchall()
        return result

    def updateStatus(self, name, mgmt_ip, status):
        """
        Update only the last_check column with
        whether or not the last polling succeeded
        """
        sql = """ UPDATE switches SET last_check = ?
                  WHERE name = ? AND mgmt_ip = ?; """
        cur = self.conn.cursor()
        cur.execute(sql, (status, name, mgmt_ip))
        self.conn.commit()
        print("DB Update completed")
        return

    def updateLastRun(self):
        """
        Updates single entry that contains last run time
        """
        sql = """ UPDATE last_update
                  SET lastrun = ?
                  WHERE id = 1;
        """
        now = datetime.now()
        timestamp = now.strftime("%B, %d, %Y %H:%M:%S")
        cur = self.conn.cursor()
        cur.execute(sql, [timestamp])
        self.conn.commit()
        return

    def getLastUpdate(self):
        """
        Return last runtime
        """
        sql = """ SELECT lastrun from last_update WHERE id = 1;
        """
        cur = self.conn.cursor()
        cur.execute(sql)
        result = cur.fetchall()
        try:
            lastupdate = result[0][0]
        except:
            lastupdate = None
        return lastupdate

    def initLastUpdate(self):
        """
        Initialize data in last_update table
        """
        sql = """ INSERT INTO last_update(lastrun) values(?); """
        if not self.getLastUpdate():
            cur = self.conn.cursor()
            cur.execute(sql, ["Never"])
            self.conn.commit()

    def close(self):
        self.conn.close()


I am getting errors which seems like related to html codes in bootstrap but can't overcome it.
attaching error

Traceback (most recent call last):
  File "data_collector_v3.py", line 308, in <module>
    run()
  File "data_collector_v3.py", line 296, in run
    updateDB(dev, ip, sysinfo, portinfo, detailedinfo)
  File "data_collector_v3.py", line 243, in updateDB
    swDB.updatePorts(device, ip, portinfo)
  File "/home/vndrtsvp1/VENV/github/switchport-web-dashboard/switchdb.py", line 147, in updatePorts
    mgmt_ip,
sqlite3.OperationalError: no such column: in_octet_zero

Thanks in advance for great codes making available to community.

Interfaces detail

Hello,
I congratulate you for your great effort and work in the development of this project, but you know that I am new to programming and I would like you to help me so that in the tab show all the interfaces (interface name, status, description and macaddress) for each switch in a new table, but I can't do the insert to the table, it just brings me the last value.
Here is my code example added to the file "data_collector.py" :

def getInterfaceDetail(device):

resp = device.send_command("show interfaces")
intdata = resp.genie_parse_output()
for iface in intdata:
    if 'Ethernet' not in iface:
        #print(f'found non-ethernet interface: {iface}')
        continue
    if 'GigabitEthernet0/0' in iface:
        #print(f'found management interface: {iface}')
        continue
    print(f"getInterfaceDetail!!!")
    inport = {}
    inport['iface'] = iface
    inport['oper_status'] = intdata[iface]['oper_status']
    inport['description'] = intdata[iface]['description']
    inport['phys_address'] = intdata[iface]['phys_address']

return inport

Result Intport

{'iface': 'GigabitEthernet1/0/1', 'oper_status': 'down', 'description': 'CONNECTION WITH 1/2/026', 'phys_address': '0064.404c.a701'}
{'iface': 'GigabitEthernet1/0/2', 'oper_status': 'down', 'description': 'CONNECTION WITH 1/6/127', 'phys_address': '0064.404c.a702'}
{'iface': 'GigabitEthernet1/0/3', 'oper_status': 'up', 'description': 'CONNECTION WITH 1/2/045', 'phys_address': '0064.404c.a703'}
{'iface': 'GigabitEthernet1/0/4', 'oper_status': 'up', 'description': 'CONNECTION WITH 1/3/050', 'phys_address': '0064.404c.a704'}
{'iface': 'GigabitEthernet1/0/5', 'oper_status': 'down', 'description': 'CONNECTION WITH 1/2/028', 'phys_address': '0064.404c.a705'}

and this is how it looks in my manually added database:

+----------------------+-------------+-------------------------+----------------+
| iface | oper_status | description | phys_address |
+----------------------+-------------+-------------------------+----------------+
| CNOC_CUICUILCO_SW01 | 10.238.1.1 | NULL | NULL |
| GigabitEthernet1/0/1 | down | CONNECTION WITH 1/2/026 | 0064.404c.a701 |
| GigabitEthernet1/0/2 | down | CONNECTION WITH 1/6/127 | 0064.404c.a702 |
| GigabitEthernet1/0/3 | up | CONNECTION WITH 1/2/045 | 0064.404c.a703 |
| GigabitEthernet1/0/4 | up | CONNECTION WITH 1/3/050 | 0064.404c.a704 |
| GigabitEthernet1/0/5 | down | CONNECTION WITH 1/2/028 | 0064.404c.a705 |
+----------------------+-------------+-------------------------+----------------+

Also I don't know how to update if an interface changes from down to up, or from up to down.

Could you help me please.

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.