Coder Social home page Coder Social logo

inductiveautomation / kindling Goto Github PK

View Code? Open in Web Editor NEW

This project forked from paul-griffith/kindling

35.0 3.0 4.0 6.58 MB

A standalone collection of utilities to help Ignition users. Features various tools to help work with Ignition's custom data export formats.

Home Page: https://inductiveautomation.github.io/kindling/

License: MIT License

Java 6.62% Kotlin 92.67% HTML 0.14% CSS 0.57%
ignition inductive-automation support troubleshooting

kindling's Introduction

Kindling

Kindling

A standalone desktop application targeted to advanced Ignition users. Features various tools to read and access Ignition's myriad data export formats.

Tools

Thread Viewer

Parses Ignition thread dump files, in JSON or plain text format. Multiple thread dumps from the same system can be opened at once and will be automatically aggregated together.

IDB Viewer

Opens Ignition .idb files (SQLite DBs) and displays a list of tables and allows arbitrary SQL queries to be executed.

Has special handling for:

  • Metrics files
  • System logs
  • Images in the configuration DB

Log Viewer

Open one (or multiple) wrapper.log files. If the output format is Ignition's default, they will be automatically parsed and presented in the same log view used for system logs. If multiple files are selected, an attempt will be made to sequence them and present as a single view.

Archive Explorer

Opens a zip file (including Ignition files like .gwbk or .modl). Allows opening other tools against the files within the zip, including the .idb files in a gateway backup, or the files in a diagnostics bundle.

Store and Forward Cache Viewer

Opens the HSQLDB file that contains the Store and Forward disk cache. Attempts to parse the Java-serialized data within into its object representation. If unable to deserialize (e.g. due to a missing class), falls back to a string explanation of the serialized data.

Note: If you encounter any issues with missing classes, please file an issue.

Alarm Cache Viewer

Opens the Java serialized .alarms_$timestamp files Ignition uses to persist alarm information between Gateway restarts. Only works for alarm caches from 8.1.20 and up gateways.

Note: If you encounter any issues with missing classes, please file an issue.

Gateway Network Diagram Viewer

Validates a Gateway Network Diagram, as exported from the Gateway webpage (see instructions below). You can load from a .json or .txt file on disk, or paste directly from the clipboard. Click the 'View Diagram in Browser' button to launch the diagram visualization in a local web browser.

To Obtain a GAN Diagram JSON (8.1.37 and below)

  1. Set the gateway.routes.status.GanRoutes logger to DEBUG.
  2. Return to the gateway network status page and view the live graph.
  3. Return to the logs and copy the JSON to the clipboard or save it to a local file.

XML Viewer

Opens Ignition XML files in a simple text view.

Has special handling for:

  • Logback configuration files, with a special interactive editor
  • Store and Forward quarantine files, with an attempt made to deserialize any Java-serialized data within

Usage

  1. Download the installer for your OS from the Downloads page: https://inductiveautomation.github.io/kindling/download.html
  2. Run the Kindling application.
  3. Open a supported file - either drag and drop directly onto the application window, click the + icon in the tab strip, or select a tool to open from the menubar.

Preferences are stored in ~/.kindling/preferences.json and can be modified within the application from the menu bar.

Development

Kindling uses Java Swing as a GUI framework, but is written almost exclusively in Kotlin, an alternate JVM language. Gradle is used as the build tool, and will automatically download the appropriate Gradle and JDK version (via the Gradle wrapper). Most IDEs (Eclipse, IntelliJ) should figure out the project structure automatically. You can directly run the main class in your IDE (MainPanel), or you can run the application via./gradlew run at the command line.

Contribution

Contributions of any kind (additional tools, polish to existing tools, test files) are welcome.

Acknowledgements

Warning

Kindling is not an official Inductive Automation product and is provided as-is with no warranty.

kindling's People

Contributors

renovate[bot] avatar paul-griffith avatar joshlha avatar cosmostevens avatar corbinh avatar ia-charrell avatar rahulvaddepalli2 avatar grossm04 avatar

Stargazers

 avatar Jarvis Coghlin / 朝霖 avatar Venel Raphaël avatar Jakub Bednarek avatar  avatar Sagnelonge Enzo avatar Tyler avatar Andy avatar  avatar  avatar Nick Hotto avatar Rodolphe G. - 0xRo avatar Daniel Schmidt avatar Richard Lee avatar  avatar Josh Dingus avatar  avatar  avatar  avatar  avatar  avatar Christopher Roberts avatar Jesse Van Hill avatar Eric Knorr avatar  avatar DCALDERONV avatar Taylor Jones avatar Cody Warren avatar Subin Adhikari avatar  avatar  avatar  avatar Patrick Mannion avatar  avatar  avatar

Watchers

DCALDERONV avatar  avatar  avatar

kindling's Issues

Remove Zip4J dependency

Base JDK stuff is sufficient for our needs, and removing a dependency is a straightforward win

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

Open

These updates have all been created already. Click a checkbox below to force a retry/rebase of any.

Detected dependencies

github-actions
.github/workflows/codeql.yml
  • actions/checkout v4
  • github/codeql-action v3
  • github/codeql-action v3
  • github/codeql-action v3
.github/workflows/dokka.yml
  • actions/checkout v4
  • actions/setup-java v4
  • peaceiris/actions-gh-pages v4
.github/workflows/pr-build.yml
  • actions/checkout v4
  • actions/setup-java v4
gradle
buildSrc/src/main/kotlin/DownloadJavadocs.kt
gradle.properties
settings.gradle.kts
  • org.gradle.toolchains.foojay-resolver-convention 0.8.0
build.gradle.kts
buildSrc/settings.gradle.kts
buildSrc/build.gradle.kts
gradle/libs.versions.toml
  • org.jetbrains.kotlinx:kotlinx-coroutines-core 1.8.1
  • org.jetbrains.kotlinx:kotlinx-coroutines-swing 1.8.1
  • org.jetbrains.kotlinx:kotlinx-serialization-json 1.6.3
  • org.xerial:sqlite-jdbc 3.46.1.0
  • ch.qos.logback:logback-classic 1.5.6
  • org.hsqldb:hsqldb 2.7.3
  • org.apache.poi:poi-ooxml 5.3.0
  • io.github.evanrupert:excelkt 1.0.2
  • com.github.weisj:jsvg 1.6.1
  • com.fasterxml.jackson.module:jackson-module-kotlin 2.17.2
  • com.fasterxml.jackson.dataformat:jackson-dataformat-xml 2.17.2
  • org.jsoup:jsoup 1.18.1
  • com.miglayout:miglayout-swing 11.4
  • com.formdev:flatlaf 3.4.1
  • com.formdev:flatlaf-extras 3.4.1
  • com.formdev:flatlaf-jide-oss 3.4.1
  • com.formdev:flatlaf-swingx 3.4.1
  • com.formdev:flatlaf-intellij-themes 3.4.1
  • com.formdev:flatlaf-fonts-roboto 2.137
  • com.formdev:flatlaf-fonts-roboto-mono 3.000
  • com.formdev:jide-oss 3.7.15
  • org.swinglabs.swingx:swingx-all 1.6.5-1
  • com.fifesoft:rsyntaxtextarea 3.5.1
  • org.jfree:jfreechart 1.5.5
  • org.slf4j:slf4j-log4j12 2.0.16
  • com.inductiveautomation.ignition:ia-gson 2.10.1
  • org.python:jython-ia 2.7.3.3
  • io.kotest:kotest-runner-junit5 5.9.1
  • io.kotest:kotest-assertions-core 5.9.1
  • org.jetbrains.kotlin.jvm 1.9.24
  • org.jetbrains.kotlin.plugin.serialization 1.9.24
  • dev.hydraulic.conveyor 1.9
  • com.diffplug.spotless 6.25.0
  • org.jetbrains.dokka 1.9.20
gradle-wrapper
gradle/wrapper/gradle-wrapper.properties
  • gradle 8.10

  • Check this box to trigger a request for Renovate to run again on this repository

Confusing UX in Log View for Top Entry Fields

The Checkbox component and Dropdown component appear to be the the same entry field. We just need some spacing, a separator, or perhaps just move the text to the left of the checkbox instead of the right.

image

Show Date and Time in Timestamp column for HSQL (and other) data

When viewing S&F data, the Timestamp column only displays the date of the record. As S&F data is very transactional, only having the date is missing a good portion of the information I am looking for when attempting to see what is in the file. I would like to see the Date & Time showing in the Timestamp column. If there are other things that show dates/times, I would like to see a similar applied there as well.

For example, in the following screenshot if I am trying to determine the exact timestamp of any record, I have yet to find a way to do it. I am trying to track when I think Ignition should have the last record consumed and posted to a DB and Kindling doesn't really have a solution for this:

example

Tree view for loggers

Alternate presentation mode for the list of loggers. Easy to implement naively; would be nice to do some work to avoid unnecessary treepaths being created, for instance.

Read-write mode for IDB connections

Open questions

  • Does this apply to IDBs opened from within a gateway backup?

Behaviors

  • MVP: Allow update queries from generic view
  • Nice to have: If primary key is present, make results table cells editable?
  • Nice to have: Allow upload of images in image view (#13)

Sort MDC Keys

In the case where my log file has numerous values for a MDC key, the list that is presented is in the order the values appear in the logs. This makes it very difficult to find a specific key when there are thousands of different potential values. I would like to see the following:

  1. The list being ordered by the items in the list
  2. The ability to type in the specific value I am looking for

Problematic .idb file is attached as an example. If I am attempting to find a specific OPC UA node address, it is a very frustrating experience.
IssueScreenshot

Ignition-QA-WIN10_Ignition_logs_20240229-1418.idb.zip

Attempt to recover malformed IDB files

SQlite provides a means to recover corrupt IDB files via a the CLI with sqlite.exe or a C language API.

We theoretically should be able to attempt a recovery with their API using JNI (Java Native Interface)

https://www.sqlite.org/recovery.html

Currently, Kindling doesn't even throw an error to the user if a SQLiteExcption occurrs. At the very least, we should provide that info.

Exception deserializing BasicDataset in CacheView

Exception in thread "AWT-EventQueue-0" java.lang.NoClassDefFoundError: org/apache/log4j/Logger
	at com.inductiveautomation.ignition.common.XMLUtil.createSafeSAXParserFactory(XMLUtil.java:76)
	at com.inductiveautomation.ignition.common.XMLReaderPool.<init>(XMLReaderPool.java:44)
	at com.inductiveautomation.ignition.common.BasicDataset.<clinit>(BasicDataset.java:33)
	at java.base/java.io.ObjectStreamClass.hasStaticInitializer(Native Method)
	at java.base/java.io.ObjectStreamClass.computeDefaultSUID(ObjectStreamClass.java:1793)
	at java.base/java.io.ObjectStreamClass$1.run(ObjectStreamClass.java:290)
	at java.base/java.io.ObjectStreamClass$1.run(ObjectStreamClass.java:288)
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:318)
	at java.base/java.io.ObjectStreamClass.getSerialVersionUID(ObjectStreamClass.java:287)
	at java.base/java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:592)
	at java.base/java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:2051)
	at java.base/java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1898)
	at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2224)
	at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1733)
	at java.base/java.io.ObjectInputStream.readArray(ObjectInputStream.java:2157)
	at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1721)
	at java.base/java.io.ObjectInputStream$FieldValues.<init>(ObjectInputStream.java:2606)
	at java.base/java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2457)
	at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2257)
	at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1733)
	at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:509)
	at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:467)
	at io.github.paulgriffith.kindling.cache.CacheView.deserialize(CacheView.kt:270)
	at io.github.paulgriffith.kindling.cache.CacheView._init_$lambda$37(CacheView.kt:379)
	at java.desktop/javax.swing.DefaultListSelectionModel.fireValueChanged(DefaultListSelectionModel.java:224)
	at java.desktop/javax.swing.DefaultListSelectionModel.fireValueChanged(DefaultListSelectionModel.java:191)
	at java.desktop/javax.swing.DefaultListSelectionModel.setValueIsAdjusting(DefaultListSelectionModel.java:728)
	at java.desktop/javax.swing.plaf.basic.BasicTableUI$Handler.setValueIsAdjusting(BasicTableUI.java:992)
	at java.desktop/javax.swing.plaf.basic.BasicTableUI$Handler.mouseReleased(BasicTableUI.java:1205)
	at java.desktop/java.awt.AWTEventMulticaster.mouseReleased(AWTEventMulticaster.java:298)
	at java.desktop/java.awt.AWTEventMulticaster.mouseReleased(AWTEventMulticaster.java:297)
	at java.desktop/java.awt.Component.processMouseEvent(Component.java:6626)
	at java.desktop/javax.swing.JComponent.processMouseEvent(JComponent.java:3389)
	at java.desktop/java.awt.Component.processEvent(Component.java:6391)
	at java.desktop/java.awt.Container.processEvent(Container.java:2266)
	at java.desktop/java.awt.Component.dispatchEventImpl(Component.java:5001)
	at java.desktop/java.awt.Container.dispatchEventImpl(Container.java:2324)
	at java.desktop/java.awt.Component.dispatchEvent(Component.java:4833)
	at java.desktop/java.awt.LightweightDispatcher.retargetMouseEvent(Container.java:4948)
	at java.desktop/java.awt.LightweightDispatcher.processMouseEvent(Container.java:4575)
	at java.desktop/java.awt.LightweightDispatcher.dispatchEvent(Container.java:4516)
	at java.desktop/java.awt.Container.dispatchEventImpl(Container.java:2310)
	at java.desktop/java.awt.Window.dispatchEventImpl(Window.java:2780)
	at java.desktop/java.awt.Component.dispatchEvent(Component.java:4833)
	at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:773)
	at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:722)
	at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:716)
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
	at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:86)
	at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:97)
	at java.desktop/java.awt.EventQueue$5.run(EventQueue.java:746)
	at java.desktop/java.awt.EventQueue$5.run(EventQueue.java:744)
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
	at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:86)
	at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:743)
	at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203)
	at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
	at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113)
	at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109)
	at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
	at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90)
Caused by: java.lang.ClassNotFoundException: org.apache.log4j.Logger
	at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
	at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520)
	... 61 more

Invalid boolean value in java.deserializer.SerializationDumper

When reading a boolean field, the readFieldValue will take a byte from the ByteBuffer and check if it's equal to zero or not. However the way this check is done is to return true if the byte is equal to zero. This creates confusion because we would assume that if the dumper says "(boolean)true" a non-zero byte was found when in reality a zero byte was present.

case 'Z' -> print("(boolean)" + (data.get() == 0));

Changing the condition to data.get() != 0 should solve the issue.

Improve tab management

  • Drag to rearrange (#95)
  • Drag to float
  • Drag back to unfloat

Basically, continue to duplicate what IntelliJ does.

How can i decode the quarantined items

for the quarantined items, i want to know where and which script caused this. it is in .xml format, and i don't know how to decode it. Seems kindling doesn't support it.
image
image
image
image

Change to file filtering broke tool opening inside of archives

21:54:49.821 [AWT-EventQueue-0] ERROR i.g.i.kindling.zip.ZipViewer - Failed to open [/module.xml]
java.lang.UnsupportedOperationException: null
	at jdk.zipfs/jdk.nio.zipfs.ZipPath.toFile(ZipPath.java:669)
	at io.github.inductiveautomation.kindling.utils.FileFilter.accept(Swing.kt:334)
	at io.github.inductiveautomation.kindling.core.Tool$Companion.find(Tool.kt:45)
	at io.github.inductiveautomation.kindling.zip.views.ToolView$Companion.maybeToolPath(ToolView.kt:41)
	at io.github.inductiveautomation.kindling.zip.ZipViewer$handlers$1$1.invoke(ZipView.kt:200)
	at io.github.inductiveautomation.kindling.zip.ZipViewer$handlers$1$1.invoke(ZipView.kt:200)
	at io.github.inductiveautomation.kindling.zip.ZipViewer.createView(ZipView.kt:214)
	at io.github.inductiveautomation.kindling.zip.ZipView.maybeAddNewTab(ZipView.kt:167)
	at io.github.inductiveautomation.kindling.zip.ZipView.access$maybeAddNewTab(ZipView.kt:46)
	at io.github.inductiveautomation.kindling.zip.ZipView$2$1$1$openIndividually$1.invoke(ZipView.kt:119)
	at io.github.inductiveautomation.kindling.zip.ZipView$2$1$1$openIndividually$1.invoke(ZipView.kt:116)
	at io.github.inductiveautomation.kindling.utils.Action.actionPerformed(Action.kt:42)
	at java.desktop/javax.swing.AbstractButton.fireActionPerformed(AbstractButton.java:1972)
	at java.desktop/javax.swing.AbstractButton$Handler.actionPerformed(AbstractButton.java:2313)
	at java.desktop/javax.swing.DefaultButtonModel.fireActionPerformed(DefaultButtonModel.java:405)
	at java.desktop/javax.swing.DefaultButtonModel.setPressed(DefaultButtonModel.java:262)
	at java.desktop/javax.swing.AbstractButton.doClick(AbstractButton.java:374)
	at java.desktop/javax.swing.plaf.basic.BasicMenuItemUI.doClick(BasicMenuItemUI.java:1028)
	at java.desktop/javax.swing.plaf.basic.BasicMenuItemUI$Handler.mouseReleased(BasicMenuItemUI.java:1072)
	at java.desktop/java.awt.Component.processMouseEvent(Component.java:6626)
	at java.desktop/javax.swing.JComponent.processMouseEvent(JComponent.java:3389)
	at java.desktop/java.awt.Component.processEvent(Component.java:6391)
	at java.desktop/java.awt.Container.processEvent(Container.java:2266)
	at java.desktop/java.awt.Component.dispatchEventImpl(Component.java:5001)
	at java.desktop/java.awt.Container.dispatchEventImpl(Container.java:2324)
	at java.desktop/java.awt.Component.dispatchEvent(Component.java:4833)
	at java.desktop/java.awt.LightweightDispatcher.retargetMouseEvent(Container.java:4948)
	at java.desktop/java.awt.LightweightDispatcher.processMouseEvent(Container.java:4575)
	at java.desktop/java.awt.LightweightDispatcher.dispatchEvent(Container.java:4516)
	at java.desktop/java.awt.Container.dispatchEventImpl(Container.java:2310)
	at java.desktop/java.awt.Window.dispatchEventImpl(Window.java:2780)
	at java.desktop/java.awt.Component.dispatchEvent(Component.java:4833)
	at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:773)
	at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:722)
	at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:716)
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
	at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:86)
	at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:97)
	at java.desktop/java.awt.EventQueue$5.run(EventQueue.java:746)
	at java.desktop/java.awt.EventQueue$5.run(EventQueue.java:744)
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
	at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:86)
	at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:743)
	at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203)
	at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
	at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113)
	at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109)
	at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
	at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90)

Project Logo

Would be nice to have a proper logo. Conveyor has nice support for rasterizing, so all we'd have to do is come up with an SVG base. Something on-theme would be nice.

Rewrite SerializationDumper into a more structured library

Currently Java serialized data is parsed through a mangled one-off script generated from the base provided in https://github.com/NickstaDB/SerializationDumper. While the code was rewritten to Java 17, it's still not very ergonomic/practical to use, and should be rewritten to act as more of a 'pure' library, ideally spitting out direct data structures that can then be visualized or parsed down to strings more thoroughly. Unit testing would also be nice. This could be a nice opportunity for a standalone library to exist as a separate project.

Additional IDB overlays

Config IDBs

  • Image Viewer, including export (#13 )
  • Modern tag export
  • Legacy tag export
  • Legacy project export

Metrics IDB

  • See what support, others might want out of this view (#81)

Logs IDB

  • Completed/available

Tag Historian IDB

  • Probably tricky to support all versions, but ideally a table + details pane model

Audit Log IDB

  • Simple table + details pane of events, similar to logging view

Alarm journal IDB

  • Simple table + details pane of events, similar to logging view

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.