Initial commit
oops, should have committed before
This commit is contained in:
commit
9e9942da19
9 changed files with 835 additions and 0 deletions
159
.gitignore
vendored
Normal file
159
.gitignore
vendored
Normal file
|
@ -0,0 +1,159 @@
|
|||
# Created by https://www.toptal.com/developers/gitignore/api/git,node
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=git,node
|
||||
|
||||
### Git ###
|
||||
# Created by git for backups. To disable backups in Git:
|
||||
# $ git config --global mergetool.keepBackup false
|
||||
*.orig
|
||||
|
||||
# Created by git when using merge tools for conflicts
|
||||
*.BACKUP.*
|
||||
*.BASE.*
|
||||
*.LOCAL.*
|
||||
*.REMOTE.*
|
||||
*_BACKUP_*.txt
|
||||
*_BASE_*.txt
|
||||
*_LOCAL_*.txt
|
||||
*_REMOTE_*.txt
|
||||
|
||||
### Node ###
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional stylelint cache
|
||||
.stylelintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variable files
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# vuepress v2.x temp and cache directory
|
||||
.temp
|
||||
|
||||
# Docusaurus cache and generated files
|
||||
.docusaurus
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
|
||||
### Node Patch ###
|
||||
# Serverless Webpack directories
|
||||
.webpack/
|
||||
|
||||
# Optional stylelint cache
|
||||
|
||||
# SvelteKit build / generate output
|
||||
.svelte-kit
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/git,node
|
399
package-lock.json
generated
Normal file
399
package-lock.json
generated
Normal file
|
@ -0,0 +1,399 @@
|
|||
{
|
||||
"name": "tfsummaryvis",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "tfsummaryvis",
|
||||
"version": "1.0.0",
|
||||
"license": "MPL-2.0",
|
||||
"dependencies": {
|
||||
"esbuild": "^0.17.8",
|
||||
"nomnoml": "^1.5.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.17.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.8.tgz",
|
||||
"integrity": "sha512-0/rb91GYKhrtbeglJXOhAv9RuYimgI8h623TplY2X+vA4EXnk3Zj1fXZreJ0J3OJJu1bwmb0W7g+2cT/d8/l/w==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.17.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.8.tgz",
|
||||
"integrity": "sha512-oa/N5j6v1svZQs7EIRPqR8f+Bf8g6HBDjD/xHC02radE/NjKHK7oQmtmLxPs1iVwYyvE+Kolo6lbpfEQ9xnhxQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-x64": {
|
||||
"version": "0.17.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.8.tgz",
|
||||
"integrity": "sha512-bTliMLqD7pTOoPg4zZkXqCDuzIUguEWLpeqkNfC41ODBHwoUgZ2w5JBeYimv4oP6TDVocoYmEhZrCLQTrH89bg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.17.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.8.tgz",
|
||||
"integrity": "sha512-ghAbV3ia2zybEefXRRm7+lx8J/rnupZT0gp9CaGy/3iolEXkJ6LYRq4IpQVI9zR97ID80KJVoUlo3LSeA/sMAg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.17.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.8.tgz",
|
||||
"integrity": "sha512-n5WOpyvZ9TIdv2V1K3/iIkkJeKmUpKaCTdun9buhGRWfH//osmUjlv4Z5mmWdPWind/VGcVxTHtLfLCOohsOXw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.17.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.8.tgz",
|
||||
"integrity": "sha512-a/SATTaOhPIPFWvHZDoZYgxaZRVHn0/LX1fHLGfZ6C13JqFUZ3K6SMD6/HCtwOQ8HnsNaEeokdiDSFLuizqv5A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.17.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.8.tgz",
|
||||
"integrity": "sha512-xpFJb08dfXr5+rZc4E+ooZmayBW6R3q59daCpKZ/cDU96/kvDM+vkYzNeTJCGd8rtO6fHWMq5Rcv/1cY6p6/0Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.17.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.8.tgz",
|
||||
"integrity": "sha512-6Ij8gfuGszcEwZpi5jQIJCVIACLS8Tz2chnEBfYjlmMzVsfqBP1iGmHQPp7JSnZg5xxK9tjCc+pJ2WtAmPRFVA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.17.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.8.tgz",
|
||||
"integrity": "sha512-v3iwDQuDljLTxpsqQDl3fl/yihjPAyOguxuloON9kFHYwopeJEf1BkDXODzYyXEI19gisEsQlG1bM65YqKSIww==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.17.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.8.tgz",
|
||||
"integrity": "sha512-8svILYKhE5XetuFk/B6raFYIyIqydQi+GngEXJgdPdI7OMKUbSd7uzR02wSY4kb53xBrClLkhH4Xs8P61Q2BaA==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.17.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.8.tgz",
|
||||
"integrity": "sha512-B6FyMeRJeV0NpyEOYlm5qtQfxbdlgmiGdD+QsipzKfFky0K5HW5Td6dyK3L3ypu1eY4kOmo7wW0o94SBqlqBSA==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.17.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.8.tgz",
|
||||
"integrity": "sha512-CCb67RKahNobjm/eeEqeD/oJfJlrWyw29fgiyB6vcgyq97YAf3gCOuP6qMShYSPXgnlZe/i4a8WFHBw6N8bYAA==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.17.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.8.tgz",
|
||||
"integrity": "sha512-bytLJOi55y55+mGSdgwZ5qBm0K9WOCh0rx+vavVPx+gqLLhxtSFU0XbeYy/dsAAD6xECGEv4IQeFILaSS2auXw==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.17.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.8.tgz",
|
||||
"integrity": "sha512-2YpRyQJmKVBEHSBLa8kBAtbhucaclb6ex4wchfY0Tj3Kg39kpjeJ9vhRU7x4mUpq8ISLXRXH1L0dBYjAeqzZAw==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.17.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.8.tgz",
|
||||
"integrity": "sha512-QgbNY/V3IFXvNf11SS6exkpVcX0LJcob+0RWCgV9OiDAmVElnxciHIisoSix9uzYzScPmS6dJFbZULdSAEkQVw==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.17.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.8.tgz",
|
||||
"integrity": "sha512-mM/9S0SbAFDBc4OPoyP6SEOo5324LpUxdpeIUUSrSTOfhHU9hEfqRngmKgqILqwx/0DVJBzeNW7HmLEWp9vcOA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.17.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.8.tgz",
|
||||
"integrity": "sha512-eKUYcWaWTaYr9zbj8GertdVtlt1DTS1gNBWov+iQfWuWyuu59YN6gSEJvFzC5ESJ4kMcKR0uqWThKUn5o8We6Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.17.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.8.tgz",
|
||||
"integrity": "sha512-Vc9J4dXOboDyMXKD0eCeW0SIeEzr8K9oTHJU+Ci1mZc5njPfhKAqkRt3B/fUNU7dP+mRyralPu8QUkiaQn7iIg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.17.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.8.tgz",
|
||||
"integrity": "sha512-0xvOTNuPXI7ft1LYUgiaXtpCEjp90RuBBYovdd2lqAFxje4sEucurg30M1WIm03+3jxByd3mfo+VUmPtRSVuOw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"sunos"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.17.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.8.tgz",
|
||||
"integrity": "sha512-G0JQwUI5WdEFEnYNKzklxtBheCPkuDdu1YrtRrjuQv30WsYbkkoixKxLLv8qhJmNI+ATEWquZe/N0d0rpr55Mg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.17.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.8.tgz",
|
||||
"integrity": "sha512-Fqy63515xl20OHGFykjJsMnoIWS+38fqfg88ClvPXyDbLtgXal2DTlhb1TfTX34qWi3u4I7Cq563QcHpqgLx8w==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.17.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.8.tgz",
|
||||
"integrity": "sha512-1iuezdyDNngPnz8rLRDO2C/ZZ/emJLb72OsZeqQ6gL6Avko/XCXZw+NuxBSNhBAP13Hie418V7VMt9et1FMvpg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.17.8",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.8.tgz",
|
||||
"integrity": "sha512-g24ybC3fWhZddZK6R3uD2iF/RIPnRpwJAqLov6ouX3hMbY4+tKolP0VMF3zuIYCaXun+yHwS5IPQ91N2BT191g==",
|
||||
"hasInstallScript": true,
|
||||
"bin": {
|
||||
"esbuild": "bin/esbuild"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/android-arm": "0.17.8",
|
||||
"@esbuild/android-arm64": "0.17.8",
|
||||
"@esbuild/android-x64": "0.17.8",
|
||||
"@esbuild/darwin-arm64": "0.17.8",
|
||||
"@esbuild/darwin-x64": "0.17.8",
|
||||
"@esbuild/freebsd-arm64": "0.17.8",
|
||||
"@esbuild/freebsd-x64": "0.17.8",
|
||||
"@esbuild/linux-arm": "0.17.8",
|
||||
"@esbuild/linux-arm64": "0.17.8",
|
||||
"@esbuild/linux-ia32": "0.17.8",
|
||||
"@esbuild/linux-loong64": "0.17.8",
|
||||
"@esbuild/linux-mips64el": "0.17.8",
|
||||
"@esbuild/linux-ppc64": "0.17.8",
|
||||
"@esbuild/linux-riscv64": "0.17.8",
|
||||
"@esbuild/linux-s390x": "0.17.8",
|
||||
"@esbuild/linux-x64": "0.17.8",
|
||||
"@esbuild/netbsd-x64": "0.17.8",
|
||||
"@esbuild/openbsd-x64": "0.17.8",
|
||||
"@esbuild/sunos-x64": "0.17.8",
|
||||
"@esbuild/win32-arm64": "0.17.8",
|
||||
"@esbuild/win32-ia32": "0.17.8",
|
||||
"@esbuild/win32-x64": "0.17.8"
|
||||
}
|
||||
},
|
||||
"node_modules/graphre": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/graphre/-/graphre-0.1.3.tgz",
|
||||
"integrity": "sha512-F4OO/+BQj3R2UB3PQOGLgdAUy+SQuwy2A5cVGELAQDJlloSyyHqRe60ESFouRf8W0K+sm6gtWKotOwZoX3BL8w=="
|
||||
},
|
||||
"node_modules/nomnoml": {
|
||||
"version": "1.5.3",
|
||||
"resolved": "https://registry.npmjs.org/nomnoml/-/nomnoml-1.5.3.tgz",
|
||||
"integrity": "sha512-2LBKi3ygeYAC9l/wowgtBRxvQGuTryIczULVk2rFbWGUvs5aX68/sH+WgL95CschnSnxucTIv9L+3xScS71obQ==",
|
||||
"dependencies": {
|
||||
"graphre": "^0.1.3"
|
||||
},
|
||||
"bin": {
|
||||
"nomnoml": "dist/nomnoml-cli.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
32
package.json
Normal file
32
package.json
Normal file
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"name": "tfsummaryvis",
|
||||
"version": "1.0.0",
|
||||
"description": "Visualise textual tensorflow summaries as a directed graph.",
|
||||
"main": "src/index.mjs",
|
||||
"scripts": {
|
||||
"test": "echo \"No tests have been written yet.\"",
|
||||
"build": "node ./src/esbuild.mjs"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/sbrl/tfsummaryvis.git"
|
||||
},
|
||||
"keywords": [
|
||||
"tensorflow",
|
||||
"data",
|
||||
"visualisation",
|
||||
"directed",
|
||||
"graph"
|
||||
],
|
||||
"author": "Starbeamrainbowlabs",
|
||||
"license": "MPL-2.0",
|
||||
"bugs": {
|
||||
"url": "https://github.com/sbrl/tfsummaryvis/issues"
|
||||
},
|
||||
"homepage": "https://github.com/sbrl/tfsummaryvis#readme",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.17.8",
|
||||
"nomnoml": "^1.5.3"
|
||||
}
|
||||
}
|
34
src/app.css
Normal file
34
src/app.css
Normal file
|
@ -0,0 +1,34 @@
|
|||
html, body {
|
||||
margin: 0; padding: 0;
|
||||
font-size: 100%;
|
||||
}
|
||||
body {
|
||||
min-height: 100vh;
|
||||
font-family: Ubuntu, sans-serif;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-template-rows: auto 1fr auto;
|
||||
grid-template-areas: "header header"
|
||||
"left right"
|
||||
"footer footer";
|
||||
}
|
||||
|
||||
h1 {
|
||||
grid-area: header;
|
||||
margin: 0.5em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#input-summary {
|
||||
grid-area: left;
|
||||
}
|
||||
|
||||
#output-graph {
|
||||
grid-area: right;
|
||||
}
|
||||
|
||||
footer {
|
||||
grid-area: footer;
|
||||
margin: 0.5em;
|
||||
}
|
32
src/app.mjs
Normal file
32
src/app.mjs
Normal file
|
@ -0,0 +1,32 @@
|
|||
"use strict";
|
||||
|
||||
import parse_summary from './lib/parse_summary.mjs';
|
||||
import make_graph from './lib/make_graph.mjs';
|
||||
|
||||
function do_visualisation(el_in, el_out) {
|
||||
console.log(el_in, el_out);
|
||||
const source = el_in.value.trim();
|
||||
|
||||
if(source.length === 0) return;
|
||||
|
||||
const summary = parse_summary(source);
|
||||
console.log(summary);
|
||||
|
||||
const svg = make_graph(summary);
|
||||
console.log(svg);
|
||||
|
||||
const container = document.createElement("div");
|
||||
container.innerHTML = svg;
|
||||
|
||||
el_out.replaceChildren(container);
|
||||
}
|
||||
|
||||
window.addEventListener("load", () => {
|
||||
const el_in = document.querySelector("#input-summary");
|
||||
const el_out = document.querySelector("#output-graph");
|
||||
|
||||
el_in.addEventListener("changed", do_visualisation.bind(this, el_in, el_out));
|
||||
el_in.addEventListener("keyup", do_visualisation.bind(this, el_in, el_out));
|
||||
|
||||
do_visualisation(el_in, el_out);
|
||||
});
|
76
src/esbuild.mjs
Normal file
76
src/esbuild.mjs
Normal file
|
@ -0,0 +1,76 @@
|
|||
"use strict";
|
||||
|
||||
import crypto from 'crypto';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
|
||||
import esbuild from 'esbuild';
|
||||
|
||||
const __dirname = import.meta.url.slice(7, import.meta.url.lastIndexOf("/"));
|
||||
const outdir = path.resolve(__dirname, "../dist");
|
||||
|
||||
/**
|
||||
* Hashes the contents of a file.
|
||||
* @ref https://stackoverflow.com/a/44643479/1460422
|
||||
* @param {string} hashName The name of the hash algorithm to use.
|
||||
* @param {string} path The path to the file to hash.
|
||||
* @return {string} The resulting hash as a hexadecimal string.
|
||||
*/
|
||||
function hash_file(hashName, path) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const hash = crypto.createHash(hashName);
|
||||
const stream = fs.createReadStream(path);
|
||||
stream.once("error", reject);
|
||||
stream.on("data", chunk => hash.update(chunk));
|
||||
stream.once("end", () => {
|
||||
stream.off("error", reject);
|
||||
resolve(hash.digest('base64'));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function do_html() {
|
||||
// We *would* use SHA3 here, but we need to use SHA2 for subresource integrity
|
||||
let algorithm = "sha256";
|
||||
let css_hash = await hash_file(algorithm, path.join(outdir, "app.css"));
|
||||
let js_hash = await hash_file(algorithm, path.join(outdir, "app.js"));
|
||||
|
||||
let html = (await fs.promises.readFile(path.join(__dirname, "index.html"), "utf-8"))
|
||||
.replace(/\{JS_HASH\}/g, js_hash)
|
||||
.replace(/\{CSS_HASH\}/g, css_hash)
|
||||
.replace(/\{JS_HASH_SHORT\}/g, js_hash.substring(0, 7).replace(/[+/=]/, ""))
|
||||
.replace(/\{CSS_HASH_SHORT\}/g, css_hash.substring(0, 7).replace(/[+/=]/, ""))
|
||||
.replace(/\{ALGO\}/g, algorithm);
|
||||
|
||||
await fs.promises.writeFile(path.join(outdir, "index.html"), html);
|
||||
}
|
||||
|
||||
(async () => {
|
||||
"use strict";
|
||||
|
||||
const result = await esbuild.build({
|
||||
entryPoints: [
|
||||
"./app.mjs",
|
||||
"./app.css",
|
||||
].map(filepath => path.resolve(__dirname, filepath)),
|
||||
outdir,
|
||||
bundle: true,
|
||||
minify: typeof process.env.NO_MINIFY === "undefined",
|
||||
sourcemap: true,
|
||||
treeShaking: true,
|
||||
loader: {
|
||||
".html": "text",
|
||||
".svg": "file",
|
||||
".woff2": "file",
|
||||
".woff": "file",
|
||||
".eot": "file",
|
||||
".ttf": "file",
|
||||
},
|
||||
external: ["fs", "path"],
|
||||
});
|
||||
if (result.errors.length > 0 || result.warnings.length > 0)
|
||||
console.log(result);
|
||||
|
||||
await do_html();
|
||||
// console.log(await esbuild.analyzeMetafile(result.metafile));
|
||||
})();
|
23
src/index.html
Normal file
23
src/index.html
Normal file
|
@ -0,0 +1,23 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Tensorflow Summary Visualisation</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Tensorflow Summary Visualisation</h1>
|
||||
|
||||
<textarea name="input-summary" id="input-summary"></textarea>
|
||||
<div id="output-graph"></div>
|
||||
|
||||
<footer>
|
||||
<p>Built with love by <a href="https://starbeamrainbowlabs.com/">Starbeamrainbowlabs</a></p>
|
||||
</footer>
|
||||
|
||||
<!---------------->
|
||||
<link rel="stylesheet" href="/app.css?hash={CSS_HASH_SHORT}" integrity="{ALGO}-{CSS_HASH}" />
|
||||
<script src="/app.js?hash={JS_HASH_SHORT}" integrity="{ALGO}-{JS_HASH}" charset="utf-8"></script>
|
||||
<!-- <link rel="stylesheet" href="/app.css" />
|
||||
<script src="/app.js"></script> -->
|
||||
</body>
|
||||
</html>
|
20
src/lib/make_graph.mjs
Normal file
20
src/lib/make_graph.mjs
Normal file
|
@ -0,0 +1,20 @@
|
|||
"use strict";
|
||||
|
||||
import nomnoml from 'nomnoml';
|
||||
|
||||
export default function make_graph(summary) {
|
||||
const result = [];
|
||||
for(const layer of summary.layers) {
|
||||
const output_shape = layer.output_shape.replace(/\[/, "\\[")
|
||||
.replace(/\]/, "\\]");
|
||||
result.push(`[${layer.name}|${layer.type}|Params: ${layer.params}|Output shape: ${output_shape}]`);
|
||||
}
|
||||
for(const edge of summary.edges) {
|
||||
result.push(`[${edge.from}] --> [${edge.to}]`);
|
||||
}
|
||||
|
||||
const nomnoml_source = result.join("\n");
|
||||
|
||||
const svg = nomnoml.renderSvg(nomnoml_source);
|
||||
return svg;
|
||||
}
|
60
src/lib/parse_summary.mjs
Normal file
60
src/lib/parse_summary.mjs
Normal file
|
@ -0,0 +1,60 @@
|
|||
"use strict";
|
||||
|
||||
|
||||
export default function parse_summary(text) {
|
||||
const lines = text.trim().split(/\r?\n/);
|
||||
|
||||
const layers_raw = lines.slice(
|
||||
lines.findIndex(line => line.startsWith("====")) + 1,
|
||||
lines.findLastIndex(line => line.startsWith("===="))
|
||||
);
|
||||
const layers = [];
|
||||
|
||||
let acc = [];
|
||||
for (const line of layers_raw) {
|
||||
if(line.trim().length == 0) {
|
||||
if(acc.length === 0) continue;
|
||||
console.log(acc.map(layer_line => layer_line.substring(65).trim()).join(""));
|
||||
// Handle parsed item
|
||||
const result = {
|
||||
name_raw: acc.map(layer_line => layer_line.substring(0, 32).trim()).join(""),
|
||||
output_shape: acc.map(layer_line => layer_line.substring(32, 53).trim()).join(""),
|
||||
params: parseInt(acc.map(layer_line => layer_line.substring(53, 65).trim()).join("")),
|
||||
connected_to: JSON.parse(acc.map(layer_line => layer_line.substring(65).trim()).join("")
|
||||
.replace(/'/g, '"'))
|
||||
.map(connected_name => connected_name.replace(/(\[0\])+$/, ""))
|
||||
};
|
||||
|
||||
result.type = result.name_raw.match(/ \(([^)]+)\)/)[1];
|
||||
result.name = result.name_raw.split(/\s+/)[0];
|
||||
|
||||
|
||||
layers.push(result);
|
||||
|
||||
acc.length = 0;
|
||||
}
|
||||
|
||||
acc.push(line);
|
||||
}
|
||||
|
||||
const edges = [];
|
||||
|
||||
for(const layer of layers) {
|
||||
for(const connection of layer.connected_to) {
|
||||
edges.push({
|
||||
from: connection,
|
||||
to: layer.name
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
title: lines[0].match(/^Model: "([^"]+)"/)[1],
|
||||
params: parseInt(lines.find(line => line.search(/^Total params:/) > -1).split(": ")[1].replace(/,/g, "")),
|
||||
params_trainable: parseInt(lines.find(line => line.search(/^Trainable params:/) > -1).split(": ")[1].replace(/,/g, "")),
|
||||
params_nontrainable: parseInt(lines.find(line => line.search(/^Non-trainable params:/) > -1).split(": ")[1].replace(/,/g, "")),
|
||||
|
||||
layers,
|
||||
edges
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue