Comparar commits
313 Commits
| Autor | SHA1 | Data | |
|---|---|---|---|
| d12ab2a352 | |||
| 149be7cd18 | |||
| e84a946302 | |||
| ce5f890cec | |||
| f9551b18c9 | |||
| 66b794cdbe | |||
| 89edea8c15 | |||
| a533669dd9 | |||
| c9935b52a3 | |||
| 1e2a634d5f | |||
| b755b78209 | |||
| 1ce0d70a25 | |||
| 819b541140 | |||
| 4fe4dd1bd4 | |||
| 4c876dd767 | |||
| 236c1aa93d | |||
| 73f7ba38ba | |||
| ff541be7a0 | |||
| 900ecae537 | |||
| b54b6110ef | |||
| 61fb3cad96 | |||
| 9fe83de22e | |||
| a1438e2993 | |||
| 1b7a6e56fd | |||
| 4d7a158973 | |||
| 012c9fe287 | |||
| 0b4f0145e7 | |||
| 4b87d5867d | |||
| e53b684b45 | |||
| c8e9dfd0e0 | |||
| 8c88bca52b | |||
| de50123046 | |||
| 619a0b8db0 | |||
| 71d79a6d07 | |||
| 4ca379f8b9 | |||
| 53ce61caa6 | |||
| 26398a38d0 | |||
| 11d0a5e113 | |||
| 1d58dd19ab | |||
| 98848ef048 | |||
| 1f75cab8eb | |||
| bbef573418 | |||
| a172af208c | |||
| bd28a61901 | |||
| 349f3c9954 | |||
| e44d74e90f | |||
| f4df050f25 | |||
| dc2df294a4 | |||
| c3f9ce7c59 | |||
| 0b43ddfb40 | |||
| 5cedd2cb30 | |||
| a6625cfadc | |||
| 1b45ae52fc | |||
| f74d65b00e | |||
| 1f735179ea | |||
| cdd46abdee | |||
| be50c8dca3 | |||
| bab0cc66de | |||
| 58c1f401a4 | |||
| c780e7a801 | |||
| 4da87b5806 | |||
| f73d9d6958 | |||
| fd96d41698 | |||
| 7ccf8dafc4 | |||
| 2ea4f71a1a | |||
| e702f5cef0 | |||
| 24d8a02fff | |||
| 1917f71856 | |||
| 81e1622621 | |||
| 2e9e5e7889 | |||
| 017223945f | |||
| 735cbd4e73 | |||
| eef307476d | |||
| ae37e17cae | |||
| 392ebc286c | |||
| 994a8b6f39 | |||
| 3da5cc990f | |||
| bdfad427eb | |||
| da3c7ea7f7 | |||
| 5a744b665a | |||
| 38c891158d | |||
| 7ddf11318f | |||
| 69b9e9e7b6 | |||
| 27c01af612 | |||
| bdfe742730 | |||
| 28beb1f77e | |||
| 779aa04f53 | |||
| 1c1095b553 | |||
| 211623ea88 | |||
| fac7964c9d | |||
| 63e6ddd82b | |||
| 964ebc9714 | |||
| 8ba2b397bb | |||
| 1960be5fec | |||
| e431763a97 | |||
| 24d10fd0c7 | |||
| b4d1e39d06 | |||
| fdb1076c93 | |||
| 09ec77d98e | |||
| 0dcb7dfe6b | |||
| 3cadcd10db | |||
| 633f9908fc | |||
| 018f28acf2 | |||
| d2129cbf81 | |||
| 8edf718201 | |||
| 71a71d09d1 | |||
| 63a1ec0095 | |||
| 997b0dd1c8 | |||
| 44de472561 | |||
| 3ba8d71d59 | |||
| d39e32f532 | |||
| 5af00247c6 | |||
| b820d71685 | |||
| 155d1903a6 | |||
| 34ee8bc267 | |||
| a31043d661 | |||
| fc69aad300 | |||
| 15a6e2195e | |||
| 19272319ab | |||
| 60014d6411 | |||
| 5c79a68a14 | |||
| 9d47c87006 | |||
| e09086522c | |||
| 80318959bb | |||
| ee70c6c9ab | |||
| 2eb037d800 | |||
| 162cefd77e | |||
| 5fc63001f0 | |||
| 16668468f4 | |||
| ffae88a168 | |||
| 25f5759c17 | |||
| 7a0eae386f | |||
| af5d74483a | |||
| 9bf7632aba | |||
| d7f3544bb3 | |||
| 19a7b2a8fa | |||
| aadd581189 | |||
| a5d00efd39 | |||
| a239c7bd44 | |||
| 5871b5058e | |||
| 3a8108b321 | |||
| b18a3b2747 | |||
| 884ba2557f | |||
| e2d3058de1 | |||
| 02739bfd62 | |||
| 89e2e387c7 | |||
| 0ac2b1b6ab | |||
| a36e8df404 | |||
| 93a89ddaa1 | |||
| 7281dcefc6 | |||
| bfc1f08c7e | |||
| ad08fce281 | |||
| bb2a67e3f6 | |||
| 3f0b7e4720 | |||
| 728c56261d | |||
| a3dd12386e | |||
| c017f9a1d7 | |||
| 6d95f21550 | |||
| 11cb9259d8 | |||
| 71f575c857 | |||
| cf4404eaf7 | |||
| 6ef0cb0bdc | |||
| e128cc408e | |||
| 1477421f1a | |||
| 671a8af458 | |||
| a4fcc73e9f | |||
| e88fb302d4 | |||
| ff1f29955a | |||
| 0b71ff6b9d | |||
| 4f3b6c5e39 | |||
| fe0c95116f | |||
| 4429cb11d4 | |||
| 56b20ab429 | |||
| 4d0f4c3609 | |||
| 209f46adb0 | |||
| 2ac374a56e | |||
| fc4c5db0ce | |||
| 1578886584 | |||
| 0927c2c270 | |||
| bd329bdfe3 | |||
| e3a499f409 | |||
| 63c78426d3 | |||
| 6ec2f69268 | |||
| 1ccd193ddd | |||
| a5d52dce33 | |||
| ca358ae7b9 | |||
| 0a1177c299 | |||
| 46bf346c5c | |||
| 8a94496e01 | |||
| d3cf339856 | |||
| 5c0c58c3be | |||
| ef607e0e76 | |||
| 305503ac7a | |||
| c76e62b976 | |||
| 554065ee11 | |||
| 7fba52ad97 | |||
| c56de24e7d | |||
| 6e16087fc3 | |||
| 8133acbb46 | |||
| d974cb8215 | |||
| 68021c1903 | |||
| 7178b89578 | |||
| 0935abe858 | |||
| 22225922e5 | |||
| 343f2ffa61 | |||
| 477d38d313 | |||
| 84e5377b8a | |||
| 1abbecc54c | |||
| af2f34545b | |||
| f7499c4599 | |||
| ad52fe7d70 | |||
| 5ddeb6f331 | |||
| 7cd3d961cf | |||
| c38b38b4a1 | |||
| 6ae73ece70 | |||
| b5512d8228 | |||
| 89c871412a | |||
| 33e28aa3f1 | |||
| 3899c3cb16 | |||
| eb4f97e9b5 | |||
| 16e37043f4 | |||
| 261ffd1eba | |||
| 9167b33e18 | |||
| 6f7f0d01e5 | |||
| c11b1fe812 | |||
| 306425cae1 | |||
| 6d70728b54 | |||
| fadb654883 | |||
| 6528bbdbc6 | |||
| 23852de607 | |||
| 9cf66a0e02 | |||
| ad19e0fb57 | |||
| 14f222bf6f | |||
| 95141d1a42 | |||
| 93aaf72372 | |||
| 47a6612ed0 | |||
| fe48af8bf4 | |||
| 78ca4f8762 | |||
| a7e08aa279 | |||
| e2980d616f | |||
| 96f2090d9e | |||
| 8a86a114db | |||
| cb757da118 | |||
| 31711e079a | |||
| 16ebb83ae6 | |||
| 65304b2645 | |||
| b44c6a104b | |||
| a8e5876f83 | |||
| 2d9d1e71c0 | |||
| 475bdfed50 | |||
| c36f12ccb2 | |||
| 0b8b5b8adf | |||
| e981c97c71 | |||
| 748461f3c6 | |||
| a51f55dd66 | |||
| 7ba2c59265 | |||
| dcc833a957 | |||
| 8c35af6554 | |||
| 70fe60a45d | |||
| 98c64fc37a | |||
| efa88194a1 | |||
| f1a5fb18e9 | |||
| 084f79c51a | |||
| 2561b1c43a | |||
| a0b20408fd | |||
| 5f3bf23a6a | |||
| 4badd0d001 | |||
| 383eda1f2b | |||
| 96edd6c825 | |||
| 4c4b2b02e2 | |||
| 428a0ec92e | |||
| b0679ae5d8 | |||
| bf3fe1ffcd | |||
| d88b758f43 | |||
| 0e6ae8f9b6 | |||
| 3426a07096 | |||
| e95f611b79 | |||
| 5d0f13c5ec | |||
| 88e1f3828e | |||
| 4de9cf29fe | |||
| 448616be12 | |||
| 93a2905534 | |||
| 0bd0bd70f2 | |||
| 1720fc869a | |||
| 41e7750dd1 | |||
| 81afc81887 | |||
| 3e9931fa3b | |||
| 5135ccef09 | |||
| 6314e00a8d | |||
| 779429e24d | |||
| a7f4eac09d | |||
| 7c645e9b23 | |||
| fc3e14644f | |||
| fd48c1b480 | |||
| 8928d0f105 | |||
| 6857da5ac7 | |||
| fee584cfde | |||
| 45fcd81739 | |||
| d831f5e032 | |||
| 65e938f489 | |||
| b9b5cea5a1 | |||
| 6c639376d1 | |||
| 58ed3048f8 | |||
| f64492061d | |||
| 552e71687d | |||
| 448de23945 | |||
| dbd00e5946 | |||
| 4d0b4ae017 | |||
| d6c201cce6 | |||
| b8bc3af524 | |||
| 72de1d44da | |||
| da6f186b52 | |||
| 5f1c50beb6 |
@@ -31,5 +31,8 @@ exclude_paths:
|
||||
- test/
|
||||
- src/common/lua.js
|
||||
- src/common/js-yaml.min.js
|
||||
- src/visualizers/widgets/Sidebar/lib/
|
||||
- src/visualizers/widgets/TextEditor/lib/
|
||||
- src/visualizers/widgets/PipelineIndex/styles/PipelineIndex.css
|
||||
- src/visualizers/widgets/LineGraph/lib/
|
||||
- src/visualizers/widgets/PipelineEditor/klay.js
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
/node_modules
|
||||
@@ -33,3 +33,8 @@ node_modules
|
||||
tmp/
|
||||
test-tmp/
|
||||
blob-local-storage/
|
||||
src/seeds/nn/hash.txt
|
||||
src/seeds/pipeline/hash.txt
|
||||
|
||||
notes/
|
||||
src/worker
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
src/worker/tmp
|
||||
.env
|
||||
*.swp
|
||||
*.swo
|
||||
**.sass-cache
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directory
|
||||
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
|
||||
node_modules
|
||||
tmp/
|
||||
test-tmp/
|
||||
blob-local-storage/
|
||||
src/seeds/nn/hash.txt
|
||||
src/seeds/pipeline/hash.txt
|
||||
|
||||
# docs
|
||||
images/
|
||||
|
||||
notes/
|
||||
src/worker
|
||||
@@ -2,5 +2,4 @@ language: node_js
|
||||
services: mongodb
|
||||
sudo: false
|
||||
node_js:
|
||||
- "4.1.1"
|
||||
- "4.2.5"
|
||||
- "6.2.1"
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# Dockerfile for running the server itself
|
||||
FROM node:6.10.1
|
||||
MAINTAINER Brian Broll <brian.broll@gmail.com>
|
||||
|
||||
RUN echo '{"allow_root": true}' > /root/.bowerrc && mkdir -p /root/.config/configstore/ && \
|
||||
echo '{}' > /root/.config/configstore/bower-github.json
|
||||
|
||||
RUN mkdir /deepforge
|
||||
ADD . /deepforge
|
||||
WORKDIR /deepforge
|
||||
|
||||
RUN cd $(npm root -g)/npm \
|
||||
&& npm install fs-extra \
|
||||
&& sed -i -e s/graceful-fs/fs-extra/ -e s/fs.rename/fs.move/ ./lib/utils/rename.js
|
||||
|
||||
RUN ln -s /deepforge/bin/deepforge /usr/local/bin
|
||||
|
||||
EXPOSE 8888
|
||||
|
||||
# Set up the data storage
|
||||
RUN deepforge config blob.dir /data/blob && \
|
||||
deepforge config mongo.dir /data/db
|
||||
|
||||
CMD ["deepforge", "start", "--server"]
|
||||
@@ -0,0 +1,65 @@
|
||||
# This has torch and cuda support
|
||||
FROM kaixhin/cuda-torch
|
||||
MAINTAINER Brian Broll <brian.broll@gmail.com>
|
||||
|
||||
# install nodejs v6
|
||||
RUN groupadd --gid 1000 node \
|
||||
&& useradd --uid 1000 --gid node --shell /bin/bash --create-home node
|
||||
|
||||
# gpg keys listed at https://github.com/nodejs/node#release-team
|
||||
RUN set -ex \
|
||||
&& for key in \
|
||||
9554F04D7259F04124DE6B476D5A82AC7E37093B \
|
||||
94AE36675C464D64BAFA68DD7434390BDBE9B9C5 \
|
||||
FD3A5288F042B6850C66B31F09FE44734EB7990E \
|
||||
71DCFD284A79C3B38668286BC97EC7A07EDE3FC1 \
|
||||
DD8F2338BAE7501E3DD5AC78C273792F7D83545D \
|
||||
B9AE9905FFD7803F25714661B63B535A4C206CA9 \
|
||||
C4F0DFFF4E8C1A8236409D08E73BC641CC11F4C8 \
|
||||
56730D5401028683275BD23C23EFEFE93C4CFFFE \
|
||||
; do \
|
||||
gpg --keyserver ha.pool.sks-keyservers.net --recv-keys "$key"; \
|
||||
done
|
||||
|
||||
ENV NPM_CONFIG_LOGLEVEL info
|
||||
ENV NODE_VERSION 6.10.1
|
||||
|
||||
RUN curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz" \
|
||||
&& curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc" \
|
||||
&& gpg --batch --decrypt --output SHASUMS256.txt SHASUMS256.txt.asc \
|
||||
&& grep " node-v$NODE_VERSION-linux-x64.tar.xz\$" SHASUMS256.txt | sha256sum -c - \
|
||||
&& tar -xJf "node-v$NODE_VERSION-linux-x64.tar.xz" -C /usr/local --strip-components=1 \
|
||||
&& rm "node-v$NODE_VERSION-linux-x64.tar.xz" SHASUMS256.txt.asc SHASUMS256.txt \
|
||||
&& ln -s /usr/local/bin/node /usr/local/bin/nodejs
|
||||
|
||||
# install deepforge
|
||||
RUN echo '{"allow_root": true}' > /root/.bowerrc && mkdir -p /root/.config/configstore/ && \
|
||||
echo '{}' > /root/.config/configstore/bower-github.json
|
||||
|
||||
RUN mkdir /deepforge
|
||||
ADD . /deepforge
|
||||
WORKDIR /deepforge
|
||||
|
||||
RUN cd $(npm root -g)/npm \
|
||||
&& npm install fs-extra \
|
||||
&& sed -i -e s/graceful-fs/fs-extra/ -e s/fs.rename/fs.move/ ./lib/utils/rename.js
|
||||
|
||||
RUN ln -s /deepforge/bin/deepforge /usr/local/bin
|
||||
|
||||
# configure the worker
|
||||
RUN deepforge config blob.dir /data/blob && \
|
||||
deepforge config mongo.dir /data/db && \
|
||||
deepforge config worker.cache.useBlob false && \
|
||||
deepforge config worker.cache.dir /deepforge/worker-cache && \
|
||||
deepforge config torch.dir /root/torch/ && \
|
||||
git config --global user.email "deepforge-worker@deepforge.org" && \
|
||||
git config --global user.name "deepforge-worker"
|
||||
|
||||
# Update torch
|
||||
RUN apt-get update && apt-get install sudo wget && \
|
||||
. /root/torch/install/bin/torch-activate && \
|
||||
cd /root/torch/ && bash /root/torch/update.sh && \
|
||||
deepforge update -t
|
||||
|
||||
ENTRYPOINT ["deepforge", "start", "--worker"]
|
||||
CMD ["http://172.17.0.1:8888"]
|
||||
@@ -1,27 +1,50 @@
|
||||
[](https://img.shields.io/badge/state-beta-yellow.svg)
|
||||
[](./LICENSE)
|
||||
[](https://travis-ci.org/dfst/deepforge)
|
||||
[](https://gitter.im/dfst/deepforge?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[](https://waffle.io/dfst/deepforge)
|
||||
[](https://travis-ci.org/deepforge-dev/deepforge)
|
||||
[](https://gitter.im/deepforge-dev/deepforge?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[](https://waffle.io/deepforge-dev/deepforge)
|
||||
|
||||
**Notice**: DeepForge is still a work in progress and is also lacking significant documentation! That being said, any contributions and/or feedback is greatly appreciated (and feel free to always ask any questions on the gitter)!
|
||||
Using DeepForge? [Let us know what you think!](https://goo.gl/forms/2pDdCPXoUvkQhVzQ2)
|
||||
|
||||
# DeepForge
|
||||
DeepForge is an open-source visual development environment for deep learning. Currently, it supports Convolutional Neural Networks but we are planning on supporting additional deep learning classifiers such as RNNs and LSTMs. Additional features include real-time collaborative editing and version control.
|
||||
DeepForge is an open-source visual development environment for deep learning providing end-to-end support for creating deep learning models. This is achieved through providing the ability to design **architectures**, create training **pipelines**, and then execute these pipelines over a cluster. Using a notebook-esque api, users can get real-time feedback about the status of any of their **executions** including compare them side-by-side in real-time.
|
||||
|
||||

|
||||
|
||||
Additional features include:
|
||||
- Graphical architecture editor
|
||||
- Training/testing pipeline creation
|
||||
- Distributed pipeline execution
|
||||
- Real-time pipeline feedback
|
||||
- Collaborative editing
|
||||
- Automatic version control.
|
||||
- Facilitates defining custom layers
|
||||
|
||||
## Quick Start
|
||||
Simply run the following command to install deepforge with its dependencies:
|
||||
|
||||
The easiest way to start deepforge is using [docker-compose](https://docs.docker.com/compose/). Using docker-compose, deepforge can be started with
|
||||
```
|
||||
curl -o- https://raw.githubusercontent.com/dfst/deepforge/master/install.sh | bash
|
||||
wget https://raw.githubusercontent.com/deepforge-dev/deepforge/master/docker-compose.yml
|
||||
docker-compose up
|
||||
```
|
||||
|
||||
Next, follow the postinstall instructions to start MongoDB and DeepForge!
|
||||
Finally, navigate to [http://localhost:8888](http://localhost:8888) to start using DeepForge! For more detailed instructions and other installation options, check out the [docs](http://deepforge.readthedocs.io/en/latest/deployment/overview.html).
|
||||
|
||||
Finally, navigate to [http://localhost:8888](http://localhost:8888) to start using DeepForge! For more, detailed instructions,check out our [wiki](https://github.com/dfst/deepforge/wiki/Installation-Guide).
|
||||
## Additional Resources
|
||||
- [Intro to DeepForge Slides](https://docs.google.com/presentation/d/10_y5O3gHXSATfjHVLJg7dOdrz-tAXNWjlxhJ5SlA0ic/edit?usp=sharing)
|
||||
- [wiki](https://github.com/deepforge-dev/deepforge/wiki) containing overview, installation, configuration and developer information
|
||||
- [Starter Kit](https://github.com/deepforge-dev/examples/tree/master/starterkit) containing example pipelines demonstrating various deepforge features
|
||||
- [Examples](https://github.com/deepforge-dev/examples)
|
||||
|
||||
- [Datamodel Developer Slides](https://docs.google.com/presentation/d/1hd3IyUlzW_TIPnzCnE-1pdz00Pw8WaIxYiOW_Hyog-M/edit#slide=id.p)
|
||||
|
||||
## Interested in contributing?
|
||||
Contributions are welcome! Either fork the project and submit some PR's or shoot me an email about getting more involved!
|
||||
Contributions are welcome! There are a couple different ways to contribute to DeepForge:
|
||||
- Provide user feedback!
|
||||
- on the [documentation](http://deepforge.readthedocs.io)
|
||||
- on deepforge and its future development: https://goo.gl/forms/2pDdCPXoUvkQhVzQ2
|
||||
- Contribute to the project directly by submitting some PR's!
|
||||
|
||||
If you have any questions, check out the [wiki](https://github.com/dfst/deepforge/wiki/) or drop me a line on the gitter!
|
||||
|
||||
|
||||
Sponsored by [Digital Reasoning](http://www.digitalreasoning.com/)
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
theme: jekyll-theme-cayman
|
||||
@@ -3,10 +3,22 @@
|
||||
|
||||
var gmeConfig = require('./config'),
|
||||
webgme = require('webgme'),
|
||||
path = require('path'),
|
||||
fs = require('fs'),
|
||||
rm_rf = require('rimraf'),
|
||||
gracefulFs = require('graceful-fs'),
|
||||
myServer;
|
||||
|
||||
process.chdir(__dirname);
|
||||
webgme.addToRequireJsPaths(gmeConfig);
|
||||
|
||||
// Patch the 'fs' module to fix 'too many files open' error
|
||||
gracefulFs.gracefulify(fs);
|
||||
|
||||
// Clear seed hash info
|
||||
['nn', 'pipeline'].map(lib => path.join(__dirname, 'src', 'seeds', lib, 'hash.txt'))
|
||||
.forEach(file => rm_rf.sync(file));
|
||||
|
||||
myServer = new webgme.standaloneServer(gmeConfig);
|
||||
myServer.start(function (err) {
|
||||
if (err) {
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
env:
|
||||
browser: true
|
||||
node: true
|
||||
mocha: true
|
||||
es6: true
|
||||
extends: 'eslint:recommended'
|
||||
rules:
|
||||
no-console:
|
||||
- 0
|
||||
indent:
|
||||
- 2
|
||||
- 4
|
||||
linebreak-style:
|
||||
- 2
|
||||
- unix
|
||||
quotes:
|
||||
- 2
|
||||
- single
|
||||
semi:
|
||||
- 2
|
||||
- always
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"torch": {
|
||||
"dir": "~/.deepforge/torch"
|
||||
},
|
||||
"blob": {
|
||||
"dir": "~/.deepforge/blob"
|
||||
},
|
||||
"worker": {
|
||||
"cache": {
|
||||
"useBlob": true,
|
||||
"dir": "~/.deepforge/worker/cache"
|
||||
},
|
||||
"dir": "~/.deepforge/worker"
|
||||
},
|
||||
"mongo": {
|
||||
"dir": "~/.deepforge/data"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,493 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
var Command = require('commander').Command,
|
||||
tcpPortUsed = require('tcp-port-used'),
|
||||
program = new Command(),
|
||||
childProcess = require('child_process'),
|
||||
rawSpawn = childProcess.spawn,
|
||||
Q = require('q'),
|
||||
execSync = childProcess.execSync,
|
||||
path = require('path'),
|
||||
fs = require('fs'),
|
||||
pkgJson = require('../package.json'),
|
||||
version = pkgJson.version,
|
||||
exists = require('exists-file'),
|
||||
DEFAULT_CONFIG = require('./config.json'),
|
||||
merge = require('lodash.merge'),
|
||||
config,
|
||||
|
||||
configDir = path.join(process.env.HOME, '.deepforge'),
|
||||
configPath = path.join(configDir, 'config.json'),
|
||||
dataPath = path.join(configDir, 'data'),
|
||||
|
||||
localConfig,
|
||||
rm_rf = require('rimraf'),
|
||||
p = dir => {
|
||||
if (typeof dir === 'string') {
|
||||
return dir.replace(/^~/, process.env.HOME); // resolve '~' to '$HOME'
|
||||
}
|
||||
return dir;
|
||||
};
|
||||
|
||||
// Check for any commands
|
||||
if (process.argv.length === 2) {
|
||||
process.argv.push('--help');
|
||||
}
|
||||
|
||||
// Create the config if it doesn't exist
|
||||
if (!exists.sync(configDir)) {
|
||||
fs.mkdirSync(configDir);
|
||||
}
|
||||
if (!exists.sync(dataPath)) {
|
||||
fs.mkdirSync(dataPath);
|
||||
}
|
||||
if (!exists.sync(configPath)) {
|
||||
fs.writeFileSync(configPath, '{\n}');
|
||||
}
|
||||
|
||||
localConfig = require(configPath);
|
||||
config = merge({}, DEFAULT_CONFIG, localConfig);
|
||||
|
||||
var getConfigValue = function(id, srcConfig) {
|
||||
var keys = id.split('.'),
|
||||
value = srcConfig || config;
|
||||
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
if (!value.hasOwnProperty(keys[i])) {
|
||||
return null;
|
||||
}
|
||||
value = value[keys[i]];
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
var storeConfig = function(id, value) {
|
||||
// load the config
|
||||
var keys = id.split('.').filter(k => k),
|
||||
lastKey = keys.pop(),
|
||||
currentObj = localConfig,
|
||||
current = getConfigValue(id),
|
||||
expType = typeof getConfigValue(id, DEFAULT_CONFIG);
|
||||
|
||||
// Check if it is a valid key
|
||||
if (current === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
if (!currentObj[keys[i]]) {
|
||||
currentObj[keys[i]] = {};
|
||||
}
|
||||
currentObj = currentObj[keys[i]];
|
||||
}
|
||||
|
||||
if (expType !== 'string') {
|
||||
try { // try to downcast
|
||||
value = JSON.parse(value);
|
||||
} catch (e) {
|
||||
console.log(`Invalid value: "${value}" (expected ${expType})`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
currentObj[lastKey] = value;
|
||||
fs.writeFileSync(configPath, JSON.stringify(localConfig, null, 2));
|
||||
return true;
|
||||
};
|
||||
|
||||
(function() { // Load config to env
|
||||
var envToConf = require('./envConfig.json');
|
||||
Object.keys(envToConf).forEach(env => {
|
||||
var cKey = envToConf[env];
|
||||
process.env[env] = process.env[env] || p(getConfigValue(cKey));
|
||||
});
|
||||
|
||||
// Special cases
|
||||
if (process.env.DEEPFORGE_WORKER_USE_BLOB === 'true' &&
|
||||
exists.sync(process.env.DEEPFORGE_BLOB_DIR)) {
|
||||
|
||||
process.env.DEEPFORGE_WORKER_CACHE = process.env.DEEPFORGE_BLOB_DIR + '/wg-content';
|
||||
}
|
||||
})();
|
||||
|
||||
program
|
||||
.version('v' + version)
|
||||
.description('Command line interface for managing deepforge');
|
||||
|
||||
var isLocalUri = function(protocol, uri) {
|
||||
return uri.indexOf(protocol + '://localhost') === 0 ||
|
||||
uri.indexOf(protocol + '://127.0.0.1') === 0;
|
||||
};
|
||||
|
||||
var checkMongo = function(args, notSilent, mongoUri) {
|
||||
// check the webgme config
|
||||
var gmeConfig = require('../config');
|
||||
|
||||
mongoUri = mongoUri || gmeConfig.mongo.uri;
|
||||
|
||||
if (isLocalUri('mongodb', mongoUri)) {
|
||||
var match = mongoUri.match(/:([0-9]+)/),
|
||||
port = '80';
|
||||
|
||||
if (match) {
|
||||
port = match[1];
|
||||
}
|
||||
|
||||
// Make sure mongo is running locally (using pgrep)
|
||||
try {
|
||||
execSync('pgrep mongod').toString();
|
||||
console.log('MongoDB is already running!');
|
||||
} catch (e) { // no pIds
|
||||
console.log('Starting MongoDB...');
|
||||
startMongo(args, port, !notSilent);
|
||||
}
|
||||
return tcpPortUsed.waitUntilUsed(+port, 100, 1000);
|
||||
} else if (notSilent) {
|
||||
console.log(`Cannot start remote mongo locally: ${mongoUri}`);
|
||||
} else {
|
||||
console.log(`Using remote mongo: ${mongoUri}`);
|
||||
}
|
||||
return Q();
|
||||
};
|
||||
|
||||
var startMongo = function(args, port, silent) {
|
||||
var opts = ['--dbpath', p(config.mongo.dir), '--port', port],
|
||||
job = rawSpawn('mongod', opts, {cwd: process.env.HOME});
|
||||
|
||||
if (!silent) {
|
||||
job.stdout.on('data',
|
||||
data => process.stdout.write(data.toString()));
|
||||
}
|
||||
job.on('error', err => {
|
||||
if (err.code === 'ENOENT') {
|
||||
console.log('Could not find MongoDB. Is it installed?');
|
||||
if (!args.mongo) {
|
||||
console.log('Otherwise, set MONGO_URI to the desired mongo uri and try again:');
|
||||
console.log('');
|
||||
console.log(' MONGO_URI=mongodb://some.other.ip:27017' +
|
||||
`/deepforge deepforge ${process.argv.slice(2).join(' ')}`);
|
||||
console.log('');
|
||||
}
|
||||
} else {
|
||||
console.log('Error encountered while starting MongoDB');
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
job.stderr.on('data', data => {
|
||||
var msg = 'mongodb: ' + data;
|
||||
process.stdout.write(msg);
|
||||
});
|
||||
job.on('exit', code => {
|
||||
if (code) {
|
||||
console.log('MongoDB closed w/ error code: ' + code);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var hasTorch = function() {
|
||||
var result = childProcess.spawnSync('th', ['--help']);
|
||||
return !result.error;
|
||||
};
|
||||
|
||||
var installTorchExtras = function() {
|
||||
// Check if rnn is installed
|
||||
var result = childProcess.spawnSync('luarocks', ['list', '--porcelain']),
|
||||
pkgs = result.stdout.toString().split('\n')
|
||||
.map(line => line.match(/^[a-zA-Z0-9]+/g))
|
||||
.map(m => m && m[0]);
|
||||
|
||||
if (pkgs.indexOf('rnn') === -1) {
|
||||
return spawn('luarocks', ['install', 'rnn']);
|
||||
} else {
|
||||
return Q();
|
||||
}
|
||||
};
|
||||
|
||||
var installTorch = function() {
|
||||
var tgtDir = p(config.torch.dir),
|
||||
args;
|
||||
|
||||
if (!hasTorch()) {
|
||||
// Try to install torch
|
||||
console.log(`Torch7 not found. Installing to ${tgtDir}...`);
|
||||
args = `clone https://github.com/torch/distro.git ${tgtDir} --recursive`.split(' ');
|
||||
|
||||
return spawn('git', args)
|
||||
.catch(result => {
|
||||
var error = result.error || result.stderr ||
|
||||
`Torch install failed with exit code ${result.code}`;
|
||||
|
||||
if (result.stderr.includes('unable to access')) {
|
||||
error = `Could not access the torch repository. Are you ` +
|
||||
`connected to the internet?\n`;
|
||||
} else if (result.code === 128) {
|
||||
error = `${tgtDir} is not empty. ` +
|
||||
'Please empty it or change the torch directory:\n' +
|
||||
'\n deepforge config torch.dir NEW/TORCH/PATH\n';
|
||||
|
||||
}
|
||||
|
||||
console.error(error);
|
||||
process.exit(result.code);
|
||||
})
|
||||
.then((code, stderr) => {
|
||||
process.chdir(tgtDir);
|
||||
return spawn('bash', ['install-deps'])
|
||||
.then(() => spawn('bash', ['install.sh'], true))
|
||||
.then(() => {
|
||||
storeConfig('torch.dir', tgtDir);
|
||||
console.log('Installed torch. Please close and ' +
|
||||
're-open your terminal to use DeepForge w/ ' +
|
||||
'torch support!');
|
||||
process.exit(0);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
return Q();
|
||||
}
|
||||
};
|
||||
|
||||
var spawn = function(cmd, args, opts) {
|
||||
var deferred = Q.defer(),
|
||||
job,
|
||||
spawnOpts = typeof opts === 'object' ? opts : null,
|
||||
forwardStdin = opts === true,
|
||||
isOpen = true,
|
||||
stderr = '',
|
||||
err;
|
||||
|
||||
args = args || [];
|
||||
job = spawnOpts ? rawSpawn(cmd, args, spawnOpts) : rawSpawn(cmd, args);
|
||||
job.stdout.on('data', data => process.stdout.write(data));
|
||||
job.stderr.on('data', data => {
|
||||
stderr += data;
|
||||
process.stderr.write(data);
|
||||
});
|
||||
|
||||
job.on('close', code => {
|
||||
isOpen = false;
|
||||
if (err || code !== 0) {
|
||||
deferred.reject({
|
||||
code: code,
|
||||
stderr: stderr,
|
||||
error: err
|
||||
});
|
||||
} else {
|
||||
deferred.resolve(code);
|
||||
}
|
||||
});
|
||||
job.on('error', e => {
|
||||
err = e;
|
||||
});
|
||||
|
||||
if (forwardStdin) {
|
||||
process.stdin.on('data', data => {
|
||||
if (isOpen) {
|
||||
job.stdin.write(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
program.command('start')
|
||||
.description('start deepforge locally (default) or specific components')
|
||||
.option('-p, --port <port>', 'specify the port to use')
|
||||
.option('-s, --server', 'start the server')
|
||||
.option('-w, --worker [url]', 'start a worker and connect to given url. Defaults to local deepforge')
|
||||
.option('-m, --mongo', 'start MongoDB')
|
||||
.action(args => {
|
||||
var main = path.join(__dirname, 'start-local.js'),
|
||||
current = Q();
|
||||
|
||||
if (args.port) {
|
||||
process.env.PORT = args.port;
|
||||
}
|
||||
|
||||
if (args.mongo) {
|
||||
current = current.then(() => checkMongo(args, true));
|
||||
}
|
||||
|
||||
if (args.server) {
|
||||
current = current
|
||||
.then(() => checkMongo(args))
|
||||
.then(() => {
|
||||
main = path.join(__dirname, '..', 'app.js');
|
||||
return spawn('node', [main]);
|
||||
});
|
||||
}
|
||||
|
||||
if (args.worker) {
|
||||
if (hasTorch()) {
|
||||
current
|
||||
.then(() => installTorchExtras())
|
||||
.then(() => {
|
||||
main = path.join(__dirname, 'start-worker.js');
|
||||
if (args.worker !== true) {
|
||||
spawn('node', [main, args.worker]);
|
||||
} else {
|
||||
spawn('node', [main]);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
installTorch();
|
||||
}
|
||||
}
|
||||
|
||||
if (!args.server && !args.worker && !args.mongo) {
|
||||
// Starting everything
|
||||
current = current.then(() => checkMongo(args));
|
||||
if (hasTorch()) {
|
||||
current.then(() => installTorchExtras())
|
||||
.then(() => spawn('node', [main]));
|
||||
} else {
|
||||
installTorch();
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// update
|
||||
program
|
||||
.command('update')
|
||||
.description('upgrade deepforge to latest version')
|
||||
.option('-g, --git', 'update tracking the git repo')
|
||||
.option('-t, --torch', 'update torch installation')
|
||||
.option('-s, --server', 'update deepforge')
|
||||
.action(args => {
|
||||
var pkg = 'deepforge',
|
||||
latestVersion;
|
||||
|
||||
// Install the project
|
||||
if (!args.torch || args.server) {
|
||||
|
||||
if (args.git) {
|
||||
pkg = pkgJson.repository.url;
|
||||
} else {
|
||||
// Check the version
|
||||
try {
|
||||
latestVersion = execSync('npm show deepforge version')
|
||||
.toString().replace(/\s+$/, '');
|
||||
|
||||
if (latestVersion === version) {
|
||||
console.log('Already up-to-date');
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('Could not retrieve the latest deepforge version');
|
||||
}
|
||||
}
|
||||
|
||||
spawn('npm', ['install', '-g', pkg])
|
||||
.then(() => {
|
||||
console.log('Upgrade successful!');
|
||||
})
|
||||
.catch(code => console.log('Upgrade failed w/ error code: ' + code));
|
||||
}
|
||||
|
||||
if (args.torch || !args.server) {
|
||||
// Update torch
|
||||
if (hasTorch()) {
|
||||
// Upgrade torch
|
||||
console.log('Upgrading torch...');
|
||||
console.log(`Checking for torch in ${config.torch.dir}`);
|
||||
// Verify that torch is installed in the config's location
|
||||
if (!exists.sync(path.join(config.torch.dir, 'update.sh'))) {
|
||||
// config is incorrect!
|
||||
console.log('Could not find torch installation. Please update the deepforge config with:');
|
||||
console.log('');
|
||||
console.log(' deepforge config torch.dir ~/path/to/torch/install');
|
||||
console.log('');
|
||||
return;
|
||||
}
|
||||
|
||||
spawn('bash', ['./update.sh'], {cwd: p(config.torch.dir)})
|
||||
.catch(err => console.log('Upgrade failed w/ error code: ' + err.code))
|
||||
.then(() => {
|
||||
console.log('About to update rnn package...');
|
||||
// Update rnn
|
||||
return spawn('luarocks', ['install', 'rnn']);
|
||||
})
|
||||
.then(() => {
|
||||
console.log('Upgrade successful!');
|
||||
})
|
||||
.catch(code => console.log('Upgrade failed w/ error code: ' + code));
|
||||
} else {
|
||||
installTorch();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// uninstall command
|
||||
program
|
||||
.command('uninstall')
|
||||
.description('uninstall deepforge from the system')
|
||||
.option('-t, --torch', 'uninstall torch')
|
||||
.option('-c, --clean', 'uninstall deepforge, torch and all associated data/config')
|
||||
.action(opts => {
|
||||
if (opts.torch || opts.clean) {
|
||||
if (opts.torch) {
|
||||
console.log(`uninstalling torch at ${p(config.torch.dir)}`);
|
||||
}
|
||||
rm_rf.sync(p(config.torch.dir));
|
||||
}
|
||||
|
||||
if (opts.clean) { // remove the .deepforge directory
|
||||
console.log('removing config and data files...');
|
||||
rm_rf.sync(p(config.mongo.dir));
|
||||
rm_rf.sync(p(configDir));
|
||||
}
|
||||
|
||||
if (!opts.torch || opts.clean) { // uninstall deepforge
|
||||
spawn('npm', ['uninstall', '-g', 'deepforge'])
|
||||
.then(() => console.log('deepforge has been uninstalled!'))
|
||||
.catch(() => console.log('uninstall failed'));
|
||||
}
|
||||
});
|
||||
|
||||
// config
|
||||
program
|
||||
.command('config [key] [value]')
|
||||
.description('read or edit config options (omit "value" to see current value)')
|
||||
.action(key => {
|
||||
var value = program.args[1],
|
||||
success;
|
||||
|
||||
if (value) { // write a value
|
||||
success = storeConfig(key, value);
|
||||
if (success) {
|
||||
console.log('Config has been updated!');
|
||||
}
|
||||
} else if (key) { // read a single value
|
||||
value = getConfigValue(key);
|
||||
if (value === null) {
|
||||
console.log(`Invalid config value: "${key}"`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof value === 'object') {
|
||||
value = JSON.stringify(value, null, 2);
|
||||
}
|
||||
|
||||
console.log(value);
|
||||
} else { // print entire config
|
||||
console.log(`Current config:\n${JSON.stringify(config, null, 2)}`);
|
||||
}
|
||||
});
|
||||
|
||||
// extensions
|
||||
program
|
||||
.command('extensions <command>', 'Manage deepforge extensions');
|
||||
|
||||
module.exports = function(cmd) {
|
||||
var cmds = cmd.split(/\s+/).filter(w => !!w);
|
||||
cmds.unshift('./bin/deepforge');
|
||||
cmds.unshift('node');
|
||||
program.parse(cmds);
|
||||
};
|
||||
module.exports.checkMongo = checkMongo;
|
||||
|
||||
if (require.main === module) {
|
||||
program.parse(process.argv);
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
var Command = require('commander').Command,
|
||||
program = new Command(),
|
||||
extender = require('../utils/extender');
|
||||
|
||||
// Supported commands
|
||||
// - add
|
||||
// - remove
|
||||
// - list
|
||||
// - update
|
||||
program
|
||||
.command('add <project>')
|
||||
.description('Add an extension to deepforge')
|
||||
.option('-n, --name <name>', 'Project name (if different from <project>)')
|
||||
.action(project => {
|
||||
console.log('loading extension from: ' + project);
|
||||
extender.install(project)
|
||||
.then(extConfig =>
|
||||
console.log(`The ${extConfig.name} extension has been added to deepforge.`))
|
||||
.fail(err => {
|
||||
console.error('Could not install extension:\n');
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
});
|
||||
|
||||
program
|
||||
.command('remove <name>').alias('rm')
|
||||
.description('Remove an extension from deepforge')
|
||||
.action(name => {
|
||||
try {
|
||||
extender.uninstall(name);
|
||||
console.log(`${name} has been successfully removed!`);
|
||||
} catch (e) {
|
||||
console.error('Could not remove extension:');
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
program
|
||||
.command('list').alias('ls')
|
||||
.description('List installed deepforge extensions')
|
||||
.action(() => {
|
||||
var allExtConfigs = extender.getExtensionsConfig(),
|
||||
types = Object.keys(allExtConfigs),
|
||||
hasContents = false,
|
||||
names;
|
||||
|
||||
for (var i = types.length; i--;) {
|
||||
names = Object.keys(allExtConfigs[types[i]]);
|
||||
if (names.length) {
|
||||
hasContents = true;
|
||||
console.log(types[i]);
|
||||
for (var j = names.length; j--;) {
|
||||
console.log(` ${names[j]}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasContents) {
|
||||
console.log('No installed extensions');
|
||||
}
|
||||
});
|
||||
|
||||
program.parse(process.argv);
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"DEEPFORGE_BLOB_DIR": "blob.dir",
|
||||
"DEEPFORGE_WORKER_CACHE": "worker.cache.dir",
|
||||
"DEEPFORGE_WORKER_DIR": "worker.dir",
|
||||
"DEEPFORGE_WORKER_USE_BLOB": "worker.cache.useBlob"
|
||||
}
|
||||
@@ -2,12 +2,20 @@
|
||||
var spawn = require('child_process').spawn,
|
||||
stdout = '',
|
||||
execJob,
|
||||
workerJob = null;
|
||||
path = require('path'),
|
||||
env = {cwd: path.join(__dirname, '..')},
|
||||
workerJob = null,
|
||||
gmeConfig = require(__dirname + '/../config');
|
||||
|
||||
// Set the cache to the blob
|
||||
if (gmeConfig.blob.type === 'FS') {
|
||||
process.env.DEEPFORGE_WORKER_CACHE = path.resolve(gmeConfig.blob.fsDir + '/wg-content');
|
||||
}
|
||||
|
||||
process.env.NODE_ENV = 'local';
|
||||
execJob = spawn('npm', [
|
||||
'start'
|
||||
]);
|
||||
execJob = spawn('node', [
|
||||
path.join(__dirname, '..', 'app.js')
|
||||
], env);
|
||||
execJob.stdout.pipe(process.stdout);
|
||||
execJob.stderr.pipe(process.stderr);
|
||||
|
||||
@@ -15,7 +23,7 @@ execJob.stdout.on('data', function(chunk) {
|
||||
if (!workerJob) {
|
||||
stdout += chunk;
|
||||
if (stdout.indexOf('DeepForge') > -1) {
|
||||
workerJob = spawn('npm', ['run', 'worker']);
|
||||
workerJob = spawn('npm', ['run', 'worker'], env);
|
||||
workerJob.stdout.pipe(process.stdout);
|
||||
workerJob.stderr.pipe(process.stderr);
|
||||
workerJob.on('close', code => code && process.exit(code));
|
||||
|
||||
@@ -4,15 +4,40 @@ var path = require('path'),
|
||||
fs = require('fs'),
|
||||
childProcess = require('child_process'),
|
||||
spawn = childProcess.spawn,
|
||||
rm_rf = require('rimraf'),
|
||||
projectConfig = require(__dirname + '/../config'),
|
||||
executorSrc = path.join(__dirname, '..', 'node_modules', 'webgme', 'src',
|
||||
'server', 'middleware', 'executor', 'worker'),
|
||||
workerPath = path.join(__dirname, '..', 'src', 'worker'),
|
||||
executorSrc = path.join(__dirname, '..', 'node_modules', '.bin', 'webgme-executor-worker'),
|
||||
id = Date.now(),
|
||||
workerRootPath = process.env.DEEPFORGE_WORKER_DIR || path.join(__dirname, '..', 'src', 'worker'),
|
||||
workerPath = path.join(workerRootPath, `worker_${id}`),
|
||||
workerConfigPath = path.join(workerPath, 'config.json'),
|
||||
workerTmp = path.join(workerPath, 'tmp'),
|
||||
address,
|
||||
config = {};
|
||||
|
||||
var createDir = function(dir) {
|
||||
try {
|
||||
fs.statSync(dir);
|
||||
} catch (e) {
|
||||
// Create dir
|
||||
fs.mkdirSync(dir);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
createDir(workerRootPath);
|
||||
createDir(workerPath);
|
||||
createDir(workerTmp);
|
||||
|
||||
// Create sym link to the node_modules
|
||||
var modules = path.join(workerRootPath, 'node_modules');
|
||||
try {
|
||||
fs.statSync(modules);
|
||||
} catch (e) {
|
||||
// Create dir
|
||||
childProcess.spawnSync('ln', ['-s', `${__dirname}/../node_modules`, modules]);
|
||||
}
|
||||
|
||||
// Check torch support
|
||||
var result = childProcess.spawnSync('th', ['--help']);
|
||||
if (result.error) {
|
||||
@@ -22,10 +47,18 @@ if (result.error) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
var cleanUp = function() {
|
||||
console.log('removing worker directory ', workerPath);
|
||||
rm_rf.sync(workerPath);
|
||||
};
|
||||
|
||||
var startExecutor = function() {
|
||||
process.on('SIGINT', cleanUp);
|
||||
process.on('uncaughtException', cleanUp);
|
||||
|
||||
// Start the executor
|
||||
var execJob = spawn('node', [
|
||||
'node_worker.js',
|
||||
executorSrc,
|
||||
workerConfigPath,
|
||||
workerTmp
|
||||
]);
|
||||
@@ -39,21 +72,13 @@ var createConfigJson = function() {
|
||||
|
||||
if (process.argv.length > 2) {
|
||||
address = process.argv[2];
|
||||
if (!/^https?:\/\//.test(address)) {
|
||||
address = 'http://' + address;
|
||||
}
|
||||
}
|
||||
|
||||
config[address] = {};
|
||||
// TODO: Check if the config already exists
|
||||
fs.writeFile(workerConfigPath, JSON.stringify(config), startExecutor);
|
||||
};
|
||||
|
||||
process.chdir(executorSrc);
|
||||
|
||||
fs.mkdir(workerTmp, function() {
|
||||
// npm install in this directory
|
||||
var npmInstall = spawn('npm', ['install']);
|
||||
npmInstall.stdout.pipe(process.stdout);
|
||||
npmInstall.stderr.pipe(process.stderr);
|
||||
npmInstall.on('close', function() {
|
||||
createConfigJson();
|
||||
});
|
||||
});
|
||||
fs.mkdir(workerTmp, createConfigJson);
|
||||
|
||||
@@ -1,23 +1,65 @@
|
||||
{
|
||||
"AutoViz": {
|
||||
"preloadIds": [
|
||||
"ArchEditor",
|
||||
"ArchIndex",
|
||||
"PipelineIndex",
|
||||
"PipelineEditor",
|
||||
"OperationEditor",
|
||||
"ExecutionView"
|
||||
],
|
||||
"visualizerOverrides": {
|
||||
"": "ForwardViz",
|
||||
"MyArtifacts": "ArtifactIndex",
|
||||
"MyArchitectures": "ArchIndex",
|
||||
"MyExecutions": "ExecutionIndex",
|
||||
"MyPipelines": "PipelineIndex"
|
||||
}
|
||||
},
|
||||
"PipelineEditor": {
|
||||
"itemName": "operation"
|
||||
},
|
||||
"ExecutionView": {
|
||||
"itemName": "job"
|
||||
},
|
||||
"ArchEditor": {
|
||||
"hotkeys": "none",
|
||||
"itemName": "layer",
|
||||
"LayerColors": {}
|
||||
},
|
||||
"BreadcrumbHeader": {
|
||||
"pathRule": "history"
|
||||
"pathRule": "history",
|
||||
"cachePrefix": "deepforge-header"
|
||||
},
|
||||
"FloatingActionButton": {
|
||||
"hideOnEmpty": true
|
||||
"hideOnEmpty": true,
|
||||
"pluginUIConfigs": {
|
||||
"GenerateArchitecture": {
|
||||
"icon": "description",
|
||||
"hotkey": "shift enter",
|
||||
"priority": -1
|
||||
},
|
||||
"ExecutePipeline": {
|
||||
"icon": "play_arrow",
|
||||
"hotkey": "shift enter",
|
||||
"color": "green",
|
||||
"priority": 1
|
||||
},
|
||||
"ImportTorch": {
|
||||
"icon": "import_export",
|
||||
"priority": -1
|
||||
}
|
||||
}
|
||||
},
|
||||
"GenericUIProjectNavigatorController": {
|
||||
"rootMenuClass": "deepforge-logo",
|
||||
"rootDisplayName": "DeepForge"
|
||||
},
|
||||
"CHFLayout": {
|
||||
"SidebarLayout": {
|
||||
"panels": [
|
||||
{
|
||||
"id": "Header",
|
||||
"panel": "BreadcrumbHeader/BreadcrumbHeaderPanel",
|
||||
"id": "WorkerHeader",
|
||||
"panel": "WorkerHeader/WorkerHeaderPanel",
|
||||
"container": "header",
|
||||
"DEBUG_ONLY": false
|
||||
},
|
||||
@@ -33,6 +75,12 @@
|
||||
"container": "center",
|
||||
"DEBUG_ONLY": false
|
||||
},
|
||||
{
|
||||
"id": "Sidebar",
|
||||
"panel": "Sidebar/SidebarPanel",
|
||||
"container": "sidebar",
|
||||
"DEBUG_ONLY": false
|
||||
},
|
||||
{
|
||||
"id": "ForgeActionButton",
|
||||
"panel": "ForgeActionButton/ForgeActionButton",
|
||||
|
||||
@@ -8,17 +8,22 @@ require('dotenv').load({silent: true});
|
||||
|
||||
// Add/overwrite any additional settings here
|
||||
config.server.port = +process.env.PORT || config.server.port;
|
||||
config.server.timeout = 0;
|
||||
config.mongo.uri = process.env.MONGO_URI || config.mongo.uri;
|
||||
config.blob.fsDir = process.env.DEEPFORGE_BLOB_DIR || config.blob.fsDir;
|
||||
|
||||
config.requirejsPaths.deepforge = './src/common';
|
||||
config.requirejsPaths.ace = './src/visualizers/widgets/TextEditor/lib/ace';
|
||||
config.seedProjects.defaultProject = 'project';
|
||||
|
||||
config.plugin.allowBrowserExecution = true;
|
||||
config.plugin.allowServerExecution = true;
|
||||
|
||||
config.executor.enable = true;
|
||||
config.executor.clearOldDataAtStartUp = true;
|
||||
|
||||
config.visualization.extraCss.push('deepforge/styles/global.css');
|
||||
|
||||
config.storage.autoMerge.enable = true;
|
||||
|
||||
validateConfig(config);
|
||||
module.exports = config;
|
||||
|
||||
@@ -7,7 +7,6 @@ var config = require('./config.default'),
|
||||
|
||||
// Turn up the worker polling rate
|
||||
config.executor.workerRefreshInterval = 150;
|
||||
config.executor.clearOldDataAtStartUp = true,
|
||||
|
||||
validateConfig(config);
|
||||
module.exports = config;
|
||||
|
||||
@@ -5,9 +5,11 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
var config = require('./config.default');
|
||||
var config = require('./config.default'),
|
||||
path = require('path');
|
||||
|
||||
config.server.port = 9001;
|
||||
config.mongo.uri = 'mongodb://127.0.0.1:27017/webgme_tests';
|
||||
config.blob.fsDir = path.join(__dirname, '..', 'test-tmp', 'blob');
|
||||
|
||||
module.exports = config;
|
||||
module.exports = config;
|
||||
|
||||
@@ -6,32 +6,38 @@
|
||||
var config = require('webgme/config/config.default'),
|
||||
validateConfig = require('webgme/config/validator');
|
||||
|
||||
|
||||
// The paths can be loaded from the webgme-setup.json
|
||||
config.plugin.basePaths.push('src/plugins');
|
||||
config.plugin.basePaths.push('node_modules/webgme-simple-nodes/src/plugins');
|
||||
config.visualization.layout.basePaths.push('node_modules/webgme-chflayout/src/layouts');
|
||||
config.visualization.decoratorPaths.push('src/decorators');
|
||||
config.visualization.decoratorPaths.push('node_modules/webgme-easydag/src/decorators');
|
||||
config.seedProjects.basePaths.push('src/seeds/nn');
|
||||
config.seedProjects.basePaths.push('src/seeds/devTests');
|
||||
config.seedProjects.basePaths.push('src/seeds/devUtilTests');
|
||||
config.seedProjects.basePaths.push('src/seeds/pipeline');
|
||||
config.seedProjects.basePaths.push('src/seeds/devPipelineTests');
|
||||
config.seedProjects.basePaths.push('src/seeds/project');
|
||||
config.seedProjects.basePaths.push('src/seeds/cifar10');
|
||||
config.plugin.basePaths.push(__dirname + '/../src/plugins');
|
||||
config.plugin.basePaths.push(__dirname + '/../node_modules/webgme-simple-nodes/src/plugins');
|
||||
config.visualization.layout.basePaths.push(__dirname + '/../src/layouts');
|
||||
config.visualization.layout.basePaths.push(__dirname + '/../node_modules/webgme-chflayout/src/layouts');
|
||||
config.visualization.decoratorPaths.push(__dirname + '/../src/decorators');
|
||||
config.visualization.decoratorPaths.push(__dirname + '/../node_modules/webgme-easydag/src/decorators');
|
||||
config.seedProjects.basePaths.push(__dirname + '/../src/seeds/nn');
|
||||
config.seedProjects.basePaths.push(__dirname + '/../src/seeds/devTests');
|
||||
config.seedProjects.basePaths.push(__dirname + '/../src/seeds/devUtilTests');
|
||||
config.seedProjects.basePaths.push(__dirname + '/../src/seeds/pipeline');
|
||||
config.seedProjects.basePaths.push(__dirname + '/../src/seeds/devPipelineTests');
|
||||
config.seedProjects.basePaths.push(__dirname + '/../src/seeds/project');
|
||||
config.seedProjects.basePaths.push(__dirname + '/../src/seeds/cifar10');
|
||||
config.seedProjects.basePaths.push(__dirname + '/../src/seeds/xor');
|
||||
config.seedProjects.basePaths.push(__dirname + '/../src/seeds/devProject');
|
||||
|
||||
|
||||
|
||||
config.visualization.panelPaths.push('node_modules/webgme-fab/src/visualizers/panels');
|
||||
config.visualization.panelPaths.push('node_modules/webgme-breadcrumbheader/src/visualizers/panels');
|
||||
config.visualization.panelPaths.push('node_modules/webgme-autoviz/src/visualizers/panels');
|
||||
config.visualization.panelPaths.push('node_modules/webgme-easydag/src/visualizers/panels');
|
||||
config.visualization.panelPaths.push('src/visualizers/panels');
|
||||
config.visualization.panelPaths.push(__dirname + '/../node_modules/webgme-fab/src/visualizers/panels');
|
||||
config.visualization.panelPaths.push(__dirname + '/../node_modules/webgme-breadcrumbheader/src/visualizers/panels');
|
||||
config.visualization.panelPaths.push(__dirname + '/../node_modules/webgme-autoviz/src/visualizers/panels');
|
||||
config.visualization.panelPaths.push(__dirname + '/../node_modules/webgme-easydag/src/visualizers/panels');
|
||||
config.visualization.panelPaths.push(__dirname + '/../src/visualizers/panels');
|
||||
|
||||
|
||||
config.rest.components['execution/logs'] = __dirname + '/../src/routers/JobLogsAPI/JobLogsAPI.js';
|
||||
config.rest.components['job/origins'] = __dirname + '/../src/routers/JobOriginAPI/JobOriginAPI.js';
|
||||
config.rest.components['execution/pulse'] = __dirname + '/../src/routers/ExecPulse/ExecPulse.js';
|
||||
|
||||
// Visualizer descriptors
|
||||
config.visualization.visualizerDescriptors.push('./src/visualizers/Visualizers.json');
|
||||
config.visualization.visualizerDescriptors.push(__dirname + '/../src/visualizers/Visualizers.json');
|
||||
// Add requirejs paths
|
||||
config.requirejsPaths = {
|
||||
'EllipseDecorator': 'node_modules/webgme-easydag/src/decorators/EllipseDecorator',
|
||||
@@ -53,7 +59,7 @@ config.requirejsPaths = {
|
||||
'widgets/FloatingActionButton': './node_modules/webgme-fab/src/visualizers/widgets/FloatingActionButton'
|
||||
};
|
||||
|
||||
config.visualization.layout.default = 'CHFLayout';
|
||||
config.visualization.layout.default = 'SidebarLayout';
|
||||
config.mongo.uri = 'mongodb://127.0.0.1:27017/deepforge';
|
||||
validateConfig(config);
|
||||
module.exports = config;
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
services:
|
||||
mongo:
|
||||
image: mongo
|
||||
volumes:
|
||||
- "$HOME/.deepforge/data:/data/db"
|
||||
server:
|
||||
environment:
|
||||
- "MONGO_URI=mongodb://mongo:27017/deepforge"
|
||||
image: deepforge/server
|
||||
ports:
|
||||
- "8888:8888"
|
||||
volumes:
|
||||
- "$HOME/.deepforge/blob:/data/blob"
|
||||
depends_on:
|
||||
- mongo
|
||||
worker:
|
||||
command: "http://server:8888"
|
||||
image: deepforge/worker
|
||||
depends_on:
|
||||
- server
|
||||
version: "2"
|
||||
@@ -0,0 +1 @@
|
||||
_build
|
||||
@@ -0,0 +1,20 @@
|
||||
# Minimal makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
SPHINXPROJ = deepforge
|
||||
SOURCEDIR = .
|
||||
BUILDDIR = _build
|
||||
|
||||
# Put it first so that "make" without argument is like "make help".
|
||||
help:
|
||||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
||||
.PHONY: help Makefile
|
||||
|
||||
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||
%: Makefile
|
||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
@@ -0,0 +1,159 @@
|
||||
import sphinx_rtd_theme
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# deepforge documentation build configuration file, created by
|
||||
# sphinx-quickstart on Mon Mar 13 18:56:27 2017.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its
|
||||
# containing dir.
|
||||
#
|
||||
# Note that not all possible configuration values are present in this
|
||||
# autogenerated file.
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#
|
||||
# import os
|
||||
# import sys
|
||||
# sys.path.insert(0, os.path.abspath('.'))
|
||||
|
||||
|
||||
# -- General configuration ------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#
|
||||
# needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = []
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix(es) of source filenames.
|
||||
# You can specify multiple suffix as a list of string:
|
||||
#
|
||||
# source_suffix = ['.rst', '.md']
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = 'DeepForge'
|
||||
copyright = '2017, Brian Broll'
|
||||
author = 'Brian Broll'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = ''
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = ''
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#
|
||||
# This is also used if you do content translation via gettext catalogs.
|
||||
# Usually you set "language" from the command line for these cases.
|
||||
language = None
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
# This patterns also effect to html_static_path and html_extra_path
|
||||
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# If true, `todo` and `todoList` produce output, else they produce nothing.
|
||||
todo_include_todos = False
|
||||
|
||||
|
||||
# -- Options for HTML output ----------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
#
|
||||
html_theme = 'sphinx_rtd_theme'
|
||||
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#
|
||||
# html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
|
||||
# -- Options for HTMLHelp output ------------------------------------------
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'deepforgedoc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output ---------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#
|
||||
# 'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#
|
||||
# 'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#
|
||||
# 'preamble': '',
|
||||
|
||||
# Latex figure (float) alignment
|
||||
#
|
||||
# 'figure_align': 'htbp',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
(master_doc, 'deepforge.tex', 'deepforge Documentation',
|
||||
'Brian Broll', 'manual'),
|
||||
]
|
||||
|
||||
|
||||
# -- Options for manual page output ---------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
(master_doc, 'deepforge', 'deepforge Documentation',
|
||||
[author], 1)
|
||||
]
|
||||
|
||||
|
||||
# -- Options for Texinfo output -------------------------------------------
|
||||
|
||||
# Grouping the document tree into Texinfo files. List of tuples
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
(master_doc, 'deepforge', 'deepforge Documentation',
|
||||
author, 'deepforge', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
Dockerized Installation
|
||||
-----------------------
|
||||
Each of the components are also available as docker containers. This page outlines the running of each of the main components as docker containers and connecting them as necessary.
|
||||
|
||||
Database
|
||||
~~~~~~~~
|
||||
First, you can start the mongo container using:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker run -d -v /abs/path/to/data:/data/db mongo
|
||||
|
||||
where :code:`/abs/path/to/data` is the path to the mongo data location on the host. If running the database in a container, you will need to get the ip address of the given container:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker inspect <container id> | grep IPAddr
|
||||
|
||||
The :code:`<container id>` is the value returned from the original :code:`docker run` command.
|
||||
|
||||
When running mongo in a docker container, it is important to mount an external volume (using the :code:`-v` flag) to be used for the actual data (otherwise the data will be lost when the container is stopped).
|
||||
|
||||
Server
|
||||
~~~~~~
|
||||
The DeepForge server can be started with
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker run -d -v $HOME/.deepforge/blob:/data/blob \
|
||||
-p 8888:8888 -e MONGO_URI=mongodb://172.17.0.2:27017/deepforge \
|
||||
deepforge/server
|
||||
|
||||
where :code:`172.17.0.2` is the ip address of the mongo container and :code:`$HOME/.deepforge/blob` is the path to use for binary DeepForge data on the host. Of course, if the mongo instance is locating at a different location, :code:`MONGO_URI` can be set to this address as well. Also, the first port (:code:`8888`) can be replaced with the desired port to expose on the host.
|
||||
|
||||
Worker
|
||||
~~~~~~
|
||||
As workers may require GPU access, they will need to use the nvidia-docker plugin. Workers can be created using
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
nvidia-docker run -d deepforge/worker http://172.17.0.1:8888
|
||||
|
||||
where :code:`http://172.17.0.1:8888` is the location of the DeepForge server to which to connect.
|
||||
|
||||
**Note**: The :code:`deepforge/worker` image is packaged with cuda 7.5. Depending upon your hardware and nvidia version, you may need to build your own docker image or run the worker natively.
|
||||
@@ -0,0 +1,130 @@
|
||||
Native Installation
|
||||
===================
|
||||
|
||||
Dependencies
|
||||
------------
|
||||
First, install `NodeJS <https://nodejs.org/en/>`_ (v6) and `MongoDB <https://www.mongodb.org/>`_. You may also need to install git if you haven't already.
|
||||
|
||||
Next, you can install DeepForge using npm:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
npm install -g deepforge
|
||||
|
||||
Now, you can check that it installed correctly:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
deepforge --version
|
||||
|
||||
DeepForge can now be started with:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
deepforge start
|
||||
|
||||
However, the first time DeepForge is started, it will make sure that the deep learning framework is installed (if it isn't found on the host system). This may require you to start DeepForge a couple times; the first time it starts it will install Torch7 and require a terminal restart to update a couple environment variables (like `PATH`). The second time it starts it will install additional torch packages but will not require a terminal restart. Finally, DeepForge will start with all the required dependencies.
|
||||
|
||||
Database
|
||||
~~~~~~~~
|
||||
Download and install MongoDB from the `website <https://www.mongodb.org/>`_. If you are planning on running MongoDB locally on the same machine as DeepForge, simply start `mongod` and continue to setting up DeepForge.
|
||||
|
||||
If you are planning on running MongoDB remotely, set the environment variable "MONGO_URI" to the URI of the Mongo instance that DeepForge will be using:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
MONGO_URI="mongodb://pathToMyMongo.com:27017/myCollection" deepforge start
|
||||
|
||||
Server
|
||||
~~~~~~
|
||||
The DeepForge server is included with the deepforge cli and can be started simply with
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
deepforge start --server
|
||||
|
||||
By default, DeepForge will start on `http://localhost:8888`. However, the port can be specified with the `--port` option. For example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
deepforge start --server --port 3000
|
||||
|
||||
Worker
|
||||
~~~~~~
|
||||
The DeepForge worker can be started with
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
deepforge start --worker
|
||||
|
||||
The worker will install dependencies the first time it is run (including torch, if it is not already installed).
|
||||
|
||||
To connect to a remote deepforge instance, add the url of the DeepForge server:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
deepforge start --worker http://myaddress.com:1234
|
||||
|
||||
Updating
|
||||
~~~~~~~~
|
||||
DeepForge can be updated with the command line interface rather simply:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
deepforge update
|
||||
|
||||
By default, this will update both DeepForge and the local torch installation. To only update DeepForge, add the `--server` flag:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
deepforge update --server
|
||||
|
||||
For more update options, check out `deepforge update --help`!
|
||||
|
||||
Manual Installation (Development)
|
||||
---------------------------------
|
||||
Installing DeepForge for development is essentially cloning the repository and then using `npm` (node package manager) to run the various start, test, etc, commands (including starting the individual components). The deepforge cli can still be used but must be referenced from `./bin/deepforge`. That is, `deepforge start` becomes `./bin/deepforge start` (from the project root).
|
||||
|
||||
DeepForge Server
|
||||
~~~~~~~~~~~~~~~~
|
||||
First, clone the repository:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
git clone https://github.com/dfst/deepforge.git
|
||||
|
||||
Then install the project dependencies:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
npm install
|
||||
|
||||
To run all components locally start with
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
./bin/deepforge start
|
||||
|
||||
and navigate to `http://localhost:8888` to start using DeepForge!
|
||||
|
||||
Alternatively, if jobs are going to be executed on an external worker, run `./bin/deepforge start -s` locally and navigate to `http://localhost:8888`.
|
||||
|
||||
DeepForge Worker
|
||||
~~~~~~~~~~~~~~~~
|
||||
If you are using `./bin/deepforge start -s` you will need to set up a DeepForge worker (`./bin/deepforge start` starts a local worker for you!). DeepForge workers are slave machines connected to DeepForge which execute the provided jobs. This allows the jobs to access the GPU, etc, and provides a number of benefits over trying to perform deep learning tasks in the browser.
|
||||
|
||||
Once DeepForge is installed on the worker, start it with
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
./bin/deepforge start -w
|
||||
|
||||
Note: If you are running the worker on a different machine, put the address of the DeepForge server as an argument to the command. For example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
./bin/deepforge start -w http://myaddress.com:1234
|
||||
|
||||
Updating
|
||||
~~~~~~~~
|
||||
Updating can be done the same as any other git project; that is, by running `git pull` from the project root. Sometimes, the dependencies need to be updated so it is recommended to run `npm install` following `git pull`.
|
||||
@@ -0,0 +1,26 @@
|
||||
Overview
|
||||
========
|
||||
|
||||
DeepForge Component Overview
|
||||
----------------------------
|
||||
DeepForge is composed of four main elements:
|
||||
|
||||
- *Server*: Main component hosting all the project information and is connected to by the clients
|
||||
- *Database*: MongoDB database containing DeepForge, job queue for the workers, etc
|
||||
- *Worker*: Slave machine performing the actual machine learning computation
|
||||
- *Client*: The connected browsers working on DeepForge projects.
|
||||
|
||||
Of course, only the *Server*, *Database* (MongoDB) and *Worker* need to be installed. If you are not going to execute any machine learning pipelines, installing the *Worker* can be skipped.
|
||||
|
||||
Component Dependencies
|
||||
----------------------
|
||||
The following dependencies are required for each component:
|
||||
|
||||
- *Server* (NodeJS v6.2.1)
|
||||
- *Database* (MongoDB v3.0.7)
|
||||
- *Worker*: NodeJS v6.2.1 (used for job management logic) and `Torch <http://torch.ch/docs/getting-started.html#>`_ (this will be installed automatically by the cli when needed)
|
||||
- *Client*: We recommend using Google Chrome and are not supporting other browsers (for now). In other words, other browsers can be used at your own risk.
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
After installing DeepForge, it can be helpful to check out `configuring DeepForge <getting_started/configuration.rst>`_
|
||||
|
Depois Largura: | Altura: | Tamanho: 37 KiB |
@@ -0,0 +1,13 @@
|
||||
Custom Data Types
|
||||
=================
|
||||
|
||||
As operation inputs and outputs are strongly typed, DeepForge supports the creation of custom data types to promote flexibility when designing complex pipelines and operations. DeepForge data types can be either primitive types or custom classes. Custom DeepForge primitive types are relatively straight-forward; they can inherit from other types and must implement a serialization and deserialization methods (which may be as simple as :code:`torch.save` and :code:`torch.load`). Custom classes are also relatively simple to define but actually contain their own methods along with serialization and deserialization functions.
|
||||
|
||||
New data types can be defined from the operation editor from the dialog for selecting input or output data for the operation. After defining a new class, this class is available from within any of the operations in the DeepForge project.
|
||||
|
||||
.. figure:: model_data_editor.png
|
||||
:align: center
|
||||
:scale: 55 %
|
||||
|
||||
Editing the serialization and deserialization for the "model" type
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
Custom Layers
|
||||
=============
|
||||
|
||||
DeepForge supports the creation of custom neural network layers using Torch7 and the easy usage of these layers in the visual architecture editor. Before creating custom layers, it is recommended to read about `creating custom layers in Torch7 <http://torch.ch/docs/developer-docs.html>`_.
|
||||
|
||||
A new custom layer can be created from the "add layer dialog" in the architecture editor. When creating a layer, DeepForge provides a code editor for creating custom neural network layers prepopulated with a basic template for defining the custom layer.
|
||||
|
||||
After defining the layer in the layer editor, DeepForge will provide this layer in the architecture editor and expose any configurable attributes for the layer. These attributes are parsed from the layer definition.
|
||||
|
||||
Best Practices
|
||||
--------------
|
||||
Here are a couple best practices to keep in mind when defining custom neural network layers:
|
||||
|
||||
- Use type assertions for layer, boolean attributes
|
||||
|
||||
- Return :code:`self` when defining setter functions
|
||||
|
||||
**Type assertions** should be used when defining layer attributes (ie, constructor arguments or arguments to a setter function). For example, consider the following layer definition for :code:`RecurrentAttention` which accepts an :code:`action` layer argument to its constructor.
|
||||
|
||||
.. code-block:: lua
|
||||
|
||||
local RecurrentAttention, parent = torch.class("nn.RecurrentAttention", "nn.AbstractSequencer")
|
||||
|
||||
function RecurrentAttention:__init(rnn, action, nStep, hiddenSize)
|
||||
parent.__init(self)
|
||||
assert(torch.isTypeOf(action, 'nn.Module'))
|
||||
assert(torch.type(nStep) == 'number')
|
||||
assert(torch.type(hiddenSize) == 'table')
|
||||
assert(torch.type(hiddenSize[1]) == 'number', "Does not support table hidden layers" )
|
||||
|
||||
self.rnn = rnn
|
||||
-- we can decorate the module with a Recursor to make it AbstractRecurrent
|
||||
self.rnn = (not torch.isTypeOf(rnn, 'nn.AbstractRecurrent')) and nn.Recursor(rnn) or rnn
|
||||
|
||||
-- samples an x,y actions for each example
|
||||
self.action = (not torch.isTypeOf(action, 'nn.AbstractRecurrent')) and nn.Recursor(action) or action
|
||||
self.hiddenSize = hiddenSize
|
||||
self.nStep = nStep
|
||||
|
||||
self.modules = {self.rnn, self.action}
|
||||
|
||||
self.output = {} -- rnn output
|
||||
self.actions = {} -- action output
|
||||
|
||||
self.forwardActions = false
|
||||
|
||||
self.gradHidden = {}
|
||||
end
|
||||
|
||||
In this example, :code:`assert(torch.isTypeOf(action, 'nn.Module'))` enforces that the :code:`action` variable is another neural network layer. After defining the layer, DeepForge will parse the layer definition and create a visual representation for use in the architecture editor. As this assertion enforces that :code:`action` is a neural network layer, DeepForge will update itself accordingly; in this case, editing the attribute will allow the user to hierarchically create nested neural network architectures to be passed as the :code:`action` argument to the constructor.
|
||||
|
||||
.. figure:: recurrent_attention.png
|
||||
:align: center
|
||||
:scale: 85 %
|
||||
|
||||
RecurrentAttention has attributes for each of the constructor arguments
|
||||
|
||||
An example of the generated visual model for the :code:`RecurrentAttention` is provided above. This layer has attributes for each of the constructor arguments defined in its definition. Clicking on the :code:`<none>` value for the :code:`action` attribute will then allow the user to provide layer inputs as shown below.
|
||||
|
||||
.. figure:: action_layer.png
|
||||
:align: center
|
||||
:scale: 55 %
|
||||
|
||||
Creating layer inputs for the "action" variable
|
||||
|
||||
The second best practice is to make sure to **return self in any setter functions**. An example of this can be found in the setters in the :code:`SpatialMaxPooling` layer shown below:
|
||||
|
||||
.. code-block:: lua
|
||||
|
||||
function SpatialMaxPooling:ceil()
|
||||
self.ceil_mode = true
|
||||
return self
|
||||
end
|
||||
|
||||
function SpatialMaxPooling:floor()
|
||||
self.ceil_mode = false
|
||||
return self
|
||||
end
|
||||
|
||||
Returning :code:`self` in setter functions is a good convention when defining neural network layers in Torch7 as it promotes simple and legible code such as
|
||||
|
||||
.. code-block:: lua
|
||||
|
||||
net:add(nn.SpatialMaxPooling(5, 5, 2, 2):ceil())
|
||||
|
||||
where :code:`net` is a container like a :code:`Sequential` layer. DeepForge enforces this convention and, if it finds a setter function (which also returns :code:`self`) in the layer definition will expose the internal variable (in this case :code:`ceil_mode`) to the user in the visual editor.
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
Custom Operations
|
||||
=================
|
||||
|
||||
In this document we will outline the basics of custom operations including the operation editor and operation feedback utilities.
|
||||
|
||||
The Basics
|
||||
----------
|
||||
Operations are used in pipelines and have named, typed inputs and outputs. When creating a pipeline, if you don't currently find an operation for the given task, you can easily create your own by selecting the `New Operation...` operation from the add operation dialog. This will create a new operation definition and open it in the operation editor. The operation editor has two main parts, the interface editor and the implementation editor.
|
||||
|
||||
.. figure:: operation_editor.png
|
||||
:align: center
|
||||
:scale: 45 %
|
||||
|
||||
Editing the "train" operation provided in the "First Steps" section
|
||||
|
||||
The interface editor is provided on the left and presents the interface as a diagram showing the input data and output data as objects flowing into or out of the given operation. Selecting the operation node in the operation interface editor will expand the node and allow the user to add or edit attributes for the given operation. These attributes are exposed when using this operation in a pipeline and can be set at design time - that is, these are set when creating the given pipeline. The interface diagram may also contain light blue nodes flowing into the operation. These nodes represent "references" that the operation accepts as input before running. When using the operation, references will appear alongside the attributes but will allow the user to select from a list of all possible targets when clicked.
|
||||
|
||||
.. figure:: operation_interface.png
|
||||
:align: center
|
||||
:scale: 85 %
|
||||
|
||||
The train operation accepts training data, an architecture and criterion and returns a trained model
|
||||
|
||||
On the right of the operation editor is the implementation editor. The implementation editor is a code editor specially tailored for programming the implementations of operations in DeepForge. This includes some autocomplete support for common globals in this context like the :code:`deepforge` and :code:`torch` globals. It also is synchronized with the interface editor and will provide input to the interface editor about unused variables, etc. These errors will present themselves as error or warning highlights on the data in the interface editor. A section of the implementation is shown below:
|
||||
|
||||
.. code:: lua
|
||||
|
||||
trainer = nn.StochasticGradient(net, criterion)
|
||||
trainer.learningRate = attributes.learningRate
|
||||
trainer.maxIteration = attributes.maxIterations
|
||||
|
||||
print('training for ' .. tostring(attributes.maxIterations) .. ' iterations (max)')
|
||||
print('learning rate is ' .. tostring(attributes.learningRate))
|
||||
print(trainer)
|
||||
|
||||
-- Adding the error graph
|
||||
graph = deepforge.Graph('Training Error') -- creating graph feedback
|
||||
errLine = graph:line('error')
|
||||
trainer.hookIteration = function(t, iter, currentErr)
|
||||
errLine:add(iter, currentErr) -- reporting the current error (will update in real time in DeepForge)
|
||||
end
|
||||
|
||||
trainer:train(trainset)
|
||||
|
||||
return {
|
||||
net = net
|
||||
}
|
||||
|
||||
The "train" operation uses the :code:`StochasticGradient` functionality from the :code:`nn` package to perform stochastic gradient descent. This operation sets all the parameters using values provided to the operation as either attributes or references. In the implementation, attributes are provided by the :code:`attributes` variable and provides access to the user defined attributes from within the implementation. References are treated similarly to operation inputs and are defined in variables of the same name. This can be seen with the :code:`net` and :code:`criterion` variables in the first line. Finally, operations return a table of their named outputs; in this example, it returns a single output named :code:`net`, that is, the trained neural network.
|
||||
|
||||
After defining the interface and implementation, we can now use the "train" operation in our pipelines! An example is shown below.
|
||||
|
||||
.. figure:: train_operation.png
|
||||
:align: center
|
||||
:scale: 85 %
|
||||
|
||||
Using the custom "train" operation in a pipeline
|
||||
|
||||
Operation feedback
|
||||
------------------
|
||||
Operations in DeepForge can generate metadata about its execution. This metadata is generated during the execution and provided back to the user in real-time. An example of this includes providing real-time plotting feedback of the loss function of a model while training. When implementing an operation in DeepForge, this metadata can be created using the :code:`deepforge` global.
|
||||
|
||||
.. figure:: graph_example.png
|
||||
:align: center
|
||||
:scale: 75 %
|
||||
|
||||
An example graph of the loss function while training a neural network
|
||||
|
||||
Detailed information about the available operation metadata types can be found in the `reference <reference/feedback_mechanisms.rst>`_.
|
||||
|
Depois Largura: | Altura: | Tamanho: 39 KiB |
|
Depois Largura: | Altura: | Tamanho: 51 KiB |
|
Depois Largura: | Altura: | Tamanho: 108 KiB |
|
Depois Largura: | Altura: | Tamanho: 16 KiB |
|
Depois Largura: | Altura: | Tamanho: 8.8 KiB |
|
Depois Largura: | Altura: | Tamanho: 12 KiB |
|
Depois Largura: | Altura: | Tamanho: 35 KiB |
@@ -0,0 +1,32 @@
|
||||
Getting Started
|
||||
===============
|
||||
|
||||
.. _Torch: http://torch.ch
|
||||
|
||||
What is DeepForge?
|
||||
------------------
|
||||
Deep learning is a very promising, yet complex, area of machine learning. This complexity can both create a barrier to entry for those wanting to get involved in deep learning as well as slow the development of those already comfortable in deep learning.
|
||||
|
||||
DeepForge is a development environment for deep learning focused on alleviating these problems. Leveraging the flexibility of Torch_, DeepForge is able to reduce the complexity of using deep learning while still providing advanced features such as defining custom layers.
|
||||
|
||||
Design Goals
|
||||
------------
|
||||
As mentioned above, DeepForge focuses on two main goals:
|
||||
|
||||
1. **Improving the efficiency** of experienced data scientists/researchers in deep learning
|
||||
2. **Lowering the barrier to entry** for newcomers to deep learning
|
||||
|
||||
It is important to highlight that although one of the goals is focused on lowering the barrier to entry, DeepForge is intended to be more than simply an educational tool; that is, it is important not to compromise on flexibility and effectiveness as a research/industry tool in order to provide an easier experience for beginners (that's what forks are for!).
|
||||
|
||||
Overview and Features
|
||||
---------------------
|
||||
DeepForge provides a collaborative, distributed development environment for deep learning. The development environment is a hybrid visual and textual programming environment. Higher levels of abstraction, such as creating architectures, use visual environments to capture the overall structure of the task while lower levels of abstraction, such as defining custom layers, utilize text environments to maintain the flexibility provided by torch.
|
||||
|
||||
Concepts and Terminology
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
- *Architecture* - neural network architecture composed of torch defined layers
|
||||
- *Operation* - essentially a function written in torch (such as `SGD`)
|
||||
- *Pipeline* - directed acyclic graph composed of operations
|
||||
- eg, a training pipeline may retrieve and normalize data, train an architecture and return the trained model
|
||||
- *Execution* - when a pipeline is run, an "execution" is created and reports the status of each operation as it is run (distributed over a number of worker machines)
|
||||
- *Artifact* - an artifact represents some data (either user uploaded or created during an execution)
|
||||
@@ -0,0 +1,63 @@
|
||||
First Steps
|
||||
===========
|
||||
DeepForge provides an example project for creating a classifier using the `CIFAR10 <https://www.kaggle.com/c/cifar-10>`_ dataset.
|
||||
|
||||
When first opening DeepForge in your browser (at `http://localhost:8888` if following the instructions from the `quick start <getting_started/installation.rst>`_), you will be prompted with a list of projects to open and provided the option to create a new project. For this example, let's click "Create new..." and name our project "hello_cifar".
|
||||
|
||||
.. figure:: create_project.png
|
||||
:align: center
|
||||
:scale: 65 %
|
||||
|
||||
Creating our "hello_cifar" example project
|
||||
|
||||
Clicking "Create" will bring us to a prompt for the "seed" for our project. Select "cifar10" from the dropdown and click "Create". This will now create our new project based on the cifar10 example provided with DeepForge.
|
||||
|
||||
.. figure:: set_seed.png
|
||||
:align: center
|
||||
:scale: 75 %
|
||||
|
||||
Selecting the "cifar10" example seed
|
||||
|
||||
In this example, we have three main pipelines: :code:`download-normalize`, :code:`train` and :code:`test`. :code:`download-normalize` downloads and prepares our data. The :code:`train` pipeline trains a neural network model on the cifar10 dataset and the :code:`test` pipeline tests our trained model on our test set from the cifar10 dataset.
|
||||
|
||||
.. figure:: pipelines.png
|
||||
:align: center
|
||||
:scale: 65 %
|
||||
|
||||
Three main pipelines in the cifar10 example project
|
||||
|
||||
First, we will have to retrieve and prepare the data by running the :code:`download-normalize` pipeline. This can be done by opening the given pipeline then selecting the `Execute Pipeline` option from the action button in the lower right. As soon as that pipeline finishes, we can now use this data to train a neural network.
|
||||
|
||||
Next, we can open the :code:`train` pipeline. Before we execute the pipeline we have to set the input training data that we will be using. This is done by selecting the :code:`Input` operation then clicking the value for the :code:`artifact` field. This will provide all the possible options for the input data; for this example, we will want to select the "trainingdata" artifact. After setting the input, we can click on the :code:`train` operation to inspect the hyperparameters we are using and the architecture we are training. Selecting the :code:`Output` operation will allow you to change the name of the resulting artifact of this operation (in this case, a trained model). Finally, we can execute this pipeline like before to train the model.
|
||||
|
||||
.. figure:: select_train_data.png
|
||||
:align: center
|
||||
:scale: 65 %
|
||||
|
||||
Selecting the training data for the input to the training pipeline
|
||||
|
||||
As this operation trains, we can view the status by viewing the running execution. The easiest way to view the running execution is by clicking the given execution from the execution tray in the bottom left when viewing the originating pipeline.
|
||||
|
||||
.. figure:: training_execution.png
|
||||
:align: center
|
||||
:scale: 65 %
|
||||
|
||||
Viewing the execution of the training pipeline
|
||||
|
||||
Once the model has been trained, we can test the given model using the :code:`test` pipeline. In this pipeline, we have a few more inputs to set: "testing data", "model to test" and the "human-readable class labels". If you aren't clear which operation provides which input, you can simply hover over it's connected port on the :code:`test` operation. This will provide a tooltip with the full name of the input.
|
||||
|
||||
.. figure:: test_pipeline.png
|
||||
:align: center
|
||||
:scale: 65 %
|
||||
|
||||
Viewing the execution of the testing pipeline
|
||||
|
||||
After setting the inputs for the :code:`test` pipeline (using the trained model and data from the first two pipelines), we can simply execute this pipeline to test our model. After executing the :code:`test` pipeline, we can view the execution and open the :code:`test` job to view the stdout for the given job. In the :code:`test` operation, this will allow us to view the printed accuracies of the model over each class.
|
||||
|
||||
.. figure:: test_results.png
|
||||
:align: center
|
||||
:scale: 65 %
|
||||
|
||||
Viewing the results of the testing operation
|
||||
|
||||
And that's it! We have just trained and tested our first neural network model using DeepForge. Although there are still a lot more advanced features that can be used, this should at least familiarize us with some of the core concepts in DeepForge.
|
||||
|
Depois Largura: | Altura: | Tamanho: 47 KiB |
@@ -0,0 +1,19 @@
|
||||
Quick Start
|
||||
===========
|
||||
The easiest way to get started quickly with DeepForge is using docker-compose. First, install `docker <https://docs.docker.com/engine/installation/>`_ and `docker-compose <https://docs.docker.com/compose/install/>`_.
|
||||
|
||||
Next, download the docker-compose file for DeepForge:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
wget https://raw.githubusercontent.com/deepforge-dev/deepforge/master/docker-compose.yml
|
||||
|
||||
Then start DeepForge using docker-compose:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker-compose up
|
||||
|
||||
and now DeepForge can be used by opening a browser to `http://localhost:8888 <http://localhost:8888>`_!
|
||||
|
||||
For detailed instructions about deployment installations, check out our `deployment installation instructions <getting_started/configuration.rst>`_
|
||||
|
Depois Largura: | Altura: | Tamanho: 6.7 KiB |
|
Depois Largura: | Altura: | Tamanho: 17 KiB |
|
Depois Largura: | Altura: | Tamanho: 44 KiB |
|
Depois Largura: | Altura: | Tamanho: 55 KiB |
|
Depois Largura: | Altura: | Tamanho: 46 KiB |
@@ -0,0 +1,40 @@
|
||||
.. DeepForge documentation master file, created by
|
||||
sphinx-quickstart on Mon Mar 13 18:56:27 2017.
|
||||
You can adapt this file completely to your liking, but it should at least
|
||||
contain the root `toctree` directive.
|
||||
|
||||
Welcome to DeepForge's documentation!
|
||||
=====================================
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
:caption: Getting Started
|
||||
|
||||
getting_started/getting_started.rst
|
||||
getting_started/quick_start.rst
|
||||
getting_started/hello_cifar.rst
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
:caption: Fundamentals
|
||||
|
||||
fundamentals/custom_operations.rst
|
||||
fundamentals/custom_layers.rst
|
||||
fundamentals/custom_data_types.rst
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
:caption: Deployment
|
||||
|
||||
deployment/overview.rst
|
||||
deployment/native.rst
|
||||
deployment/dockerized.rst
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
:caption: Reference
|
||||
|
||||
reference/cli.rst
|
||||
reference/configuration.rst
|
||||
reference/operation_feedback.rst
|
||||
reference/extensions.rst
|
||||
@@ -0,0 +1,91 @@
|
||||
Command Line Interface
|
||||
======================
|
||||
|
||||
This document outlines the functionality of the deepforge command line interface (provided after installing deepforge with :code:`npm install -g deepforge`).
|
||||
|
||||
- Installation Configuration
|
||||
- Starting DeepForge or Components
|
||||
- Installing and Upgrading Torch
|
||||
- Update or Uninstall DeepForge
|
||||
- Managing Extensions
|
||||
|
||||
Installation Configuration
|
||||
--------------------------
|
||||
Installation configuration including the installation location of Torch7 and data storage locations. These can be edited using the :code:`deepforge config` command as shown in the following examples:
|
||||
|
||||
Printing all the configuration settings:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
deepforge config
|
||||
|
||||
|
||||
Printing the value of a configuration setting:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
deepforge config torch.dir
|
||||
|
||||
|
||||
Setting a configuration option, such as :code:`torch.dir` can be done with:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
deepforge config torch.dir /some/new/directory
|
||||
|
||||
|
||||
For more information about the configuration settings, check out the `configuration <configuration.rst>`_ page.
|
||||
|
||||
|
||||
Starting DeepForge Components
|
||||
-----------------------------
|
||||
DeepForge components, such as the server or the workers, can be started with the :code:`deepforge start` command. By default, this command will start all the necessary components to run including the server, a mongo database (if applicable) and a worker.
|
||||
|
||||
The server can be started by itself using
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
deepforge start --server
|
||||
|
||||
|
||||
The worker can be started by itself using
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
deepforge start --worker http://154.95.87.1:7543
|
||||
|
||||
|
||||
where `http://154.95.87.1:7543` is the url of the deepforge server.
|
||||
|
||||
Installing and Upgrading Torch7
|
||||
-------------------------------
|
||||
Torch7 is lazily installed when starting a worker (if torch isn't already installed) with the rnn package. This installation can be manually updated as described in the update and installation section.
|
||||
|
||||
Update/Uninstall DeepForge
|
||||
--------------------------
|
||||
DeepForge can be updated or uninstalled using
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
deepforge update
|
||||
|
||||
|
||||
The torch installation can be updated using
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
deepforge update --torch
|
||||
|
||||
|
||||
DeepForge can be uninstalled using :code:`deepforge uninstall`
|
||||
|
||||
Managing Extensions
|
||||
-------------------
|
||||
DeepForge extensions can be installed and removed using the :code:`deepforge extensions` subcommand. Extensions can be added, removed and listed as shown below
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
deepforge extensions add https://github.com/example/some-extension
|
||||
deepforge extensions remove some-extension
|
||||
deepforge extensions list
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
Configuration
|
||||
=============
|
||||
|
||||
Configuration of deepforge is done through the `deepforge config` command from the command line interface. To see all config options, simply run `deepforge config` with no additional arguments. This will print a JSON representation of the configuration settings similar to:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
Current config:
|
||||
{
|
||||
"torch": {
|
||||
"dir": "/home/irishninja/.deepforge/torch"
|
||||
},
|
||||
"blob": {
|
||||
"dir": "/home/irishninja/.deepforge/blob"
|
||||
},
|
||||
"worker": {
|
||||
"cache": {
|
||||
"useBlob": true,
|
||||
"dir": "~/.deepforge/worker/cache"
|
||||
},
|
||||
"dir": "~/.deepforge/worker"
|
||||
},
|
||||
"mongo": {
|
||||
"dir": "~/.deepforge/data"
|
||||
}
|
||||
}
|
||||
|
||||
Setting an attribute, say `worker.cache.dir`, is done as follows
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
deepforge config worker.cache.dir /tmp
|
||||
|
||||
Environment Variables
|
||||
---------------------
|
||||
Most settings have a corresponding environment variable which can be used to override the value set in the cli's configuration. This allows the values to be temporarily set for a single run. For example, starting a worker with a different cache than set in `worker.cache.dir` can be done with:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
DEEPFORGE_WORKER_CACHE=/tmp deepforge start -w
|
||||
|
||||
The complete list of the environment variable overrides for the configuration options can be found `here <https://github.com/deepforge-dev/deepforge/blob/master/bin/envConfig.json>`_.
|
||||
|
||||
Settings
|
||||
--------
|
||||
|
||||
torch.dir
|
||||
~~~~~~~~~
|
||||
The path to the local installation of torch to be used by the deepforge worker. This is used when installing, upgrading and removing the local torch installation
|
||||
|
||||
blob.dir
|
||||
~~~~~~~~
|
||||
The path to the blob (large file storage containing models, datasets, etc) to be used by the deepforge server.
|
||||
|
||||
This can be overridden with the `DEEPFORGE_BLOB_DIR` environment variable.
|
||||
|
||||
worker.dir
|
||||
~~~~~~~~~~
|
||||
The path to the directory used for worker executions. The workers will run the executions from this directory.
|
||||
|
||||
This can be overridden with the `DEEPFORGE_WORKER_DIR` environment variable.
|
||||
|
||||
mongo.dir
|
||||
~~~~~~~~~
|
||||
The path to use for the `--dbpath` option of mongo if starting mongo using the command line interface. That is, if the MONGO_URI is set to a local uri and the cli is starting the deepforge server, the cli will check to verify that an instance of mongo is running locally. If not, it will start it on the given port and use this setting for the `--dbpath` setting of mongod.
|
||||
|
||||
worker.cache.dir
|
||||
~~~~~~~~~~~~~~~~
|
||||
The path to the worker cache directory.
|
||||
|
||||
This can be overridden with the `DEEPFORGE_WORKER_CACHE` environment variable.
|
||||
|
||||
worker.cache.useBlob
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
When running the worker on the same machine as the server, this allows the worker to use the blob as a cache and simply create symbolic links to the data (eg, training data, models) to prevent having to even perform a copy of the data on the given machine.
|
||||
|
||||
This can be overridden with the `DEEPFORGE_WORKER_USE_BLOB` environment variable.
|
||||
@@ -0,0 +1,53 @@
|
||||
Operation Feedback
|
||||
==================
|
||||
|
||||
DeepForge provides the `deepforge` global object in operation implementations for providing feedback during the execution. The various types of metadata are provided and discussed below.
|
||||
|
||||
Graphs
|
||||
------
|
||||
Real-time graphs can be created using the graph constructor:
|
||||
|
||||
.. code-block:: lua
|
||||
|
||||
local graph = deepforge.Graph('My Graph') -- created a new graph called "My Graph"
|
||||
|
||||
After creating a graph, lines can be added similarly.
|
||||
|
||||
.. code-block:: lua
|
||||
|
||||
local line1 = graph:line('first line') -- created a new line called "first line"
|
||||
local line2 = graph:line('second line') -- created a second line called "second line"
|
||||
|
||||
Finally, points can be added to the lines by calling the `:add` method on the line and passing the x and y values for the given point.
|
||||
|
||||
.. code-block:: lua
|
||||
|
||||
line1:add(1, 3) -- adding point (1, 3) to line1
|
||||
line2:add(1, 4) -- adding point (1, 4) to line2
|
||||
|
||||
line1:add(2, 5) -- adding point (2, 5) to line1
|
||||
line2:add(2, 6) -- adding point (2, 6) to line2
|
||||
|
||||
Graphs can then label their axis as follows:
|
||||
|
||||
.. code-block:: lua
|
||||
|
||||
graph:xlabel('x axis') -- label the x axis "x axis"
|
||||
graph:ylabel('y axis') -- label the y axis "y axis"
|
||||
|
||||
|
||||
Images
|
||||
------
|
||||
Images can be created using:
|
||||
|
||||
.. code-block:: lua
|
||||
|
||||
local image = deepforge.Image('My Example Image', imageTensor)
|
||||
|
||||
The first argument is the title of the image and the second argument is the tensor for the image (optional). Both the title and the tensor can be updated during execution as follows.
|
||||
|
||||
.. code-block:: lua
|
||||
|
||||
image:title('My New Title') -- updating the image title
|
||||
image:update(newTensor) -- updating the displayed image
|
||||
|
||||
|
Depois Largura: | Altura: | Tamanho: 681 KiB |
@@ -5,13 +5,6 @@
|
||||
command -v git >/dev/null 2>&1 || { echo >&2 "I require git but it's not installed. Aborting."; exit 1; }
|
||||
|
||||
echo >&2 "Checking DeepForge dependencies...";
|
||||
command -v th >/dev/null 2>&1 || {
|
||||
# No torch!
|
||||
echo >&2 "Torch is not found. Installing...";
|
||||
git clone https://github.com/torch/distro.git ~/torch --recursive;
|
||||
cd ~/torch; bash install-deps;
|
||||
./install.sh;
|
||||
}
|
||||
|
||||
# profile (bash, zsh, profile, etc) borrowed from nvm's installer
|
||||
detect_profile() {
|
||||
@@ -48,51 +41,51 @@ detect_profile() {
|
||||
}
|
||||
detect_profile
|
||||
|
||||
set_node_version() {
|
||||
# Install nodejs v6.2.1
|
||||
echo "Installing NodeJS v6.2.1"
|
||||
nvm install v6.2.1
|
||||
nvm alias default v6.2.1
|
||||
|
||||
# Install npm@2
|
||||
npm install npm@2 -g
|
||||
}
|
||||
|
||||
command -v node >/dev/null 2>&1 || {
|
||||
# No node! Install nvm
|
||||
echo >&2 "NodeJS is not found. Installing (using nvm)...";
|
||||
curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.31.1/install.sh | bash;
|
||||
source $DETECTED_PROFILE
|
||||
. $NVM_DIR/nvm.sh
|
||||
|
||||
# Install nodejs v6.2.0
|
||||
echo "Installing nodejs v6.2.0"
|
||||
nvm install v6.2.0
|
||||
nvm alias default v6.2.0
|
||||
|
||||
# Install npm@2
|
||||
npm install npm@2 -g
|
||||
|
||||
set_node_version
|
||||
}
|
||||
|
||||
command -v node >/dev/null 2>&1 || {
|
||||
# No mongod!
|
||||
echo >&2 "MongoDB is not found. Installing...";
|
||||
if [[ `uname` == "Darwin" ]]; then
|
||||
brew install mongodb
|
||||
else
|
||||
NEEDS_MONGO=true
|
||||
fi
|
||||
# Check node version supports arrow fns and string templates
|
||||
node -e '() => console.log(`print "3": ${1+2}`)' >/dev/null 2>&1 || {
|
||||
echo "Unsupported version of NodeJS."
|
||||
echo ""
|
||||
echo "Please update NodeJS to version 4.x.x or later (6.x.x recommended)"
|
||||
exit 1
|
||||
}
|
||||
|
||||
echo >&2 "Installing DeepForge...";
|
||||
|
||||
# Clone deepforge into ~/deepforge
|
||||
git clone https://github.com/dfst/deepforge ~/deepforge
|
||||
cd ~/deepforge
|
||||
npm install
|
||||
npm install -g deepforge
|
||||
|
||||
mkdir ~/deepforge/data 2> /dev/null
|
||||
echo "Final Installation Steps:"
|
||||
echo " 1) Close and re-open your terminal"
|
||||
echo " (or run \"source $DETECTED_PROFILE\")"
|
||||
|
||||
if [[ $NEEDS_MONGO ]]; then
|
||||
echo "DeepForge is installed! To run it:"
|
||||
echo " 1) Install MongoDB for your OS"
|
||||
echo " 2) Install MongoDB for your OS"
|
||||
echo " (available at https://www.mongodb.com/download-center)"
|
||||
echo " 2) make sure MongoDB is running locally"
|
||||
echo " (start mongo w/ \"mongod --dbpath ~/deepforge/data\")"
|
||||
echo " 3) Run \"npm run local\" from ~/deepforge"
|
||||
else
|
||||
echo "DeepForge is installed! To run it:"
|
||||
echo " 1) make sure MongoDB is running locally"
|
||||
echo " (start mongo w/ \"mongod --dbpath ~/deepforge/data\")"
|
||||
echo " 2) Run \"npm run local\" from ~/deepforge"
|
||||
echo " Note: for custom installs, this may not be required"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Then run DeepForge!"
|
||||
echo " 1) run \"deepforge start\""
|
||||
echo " 2) open a browser to http://localhost:8888"
|
||||
echo " 3) start building neural nets!"
|
||||
|
||||
@@ -1,31 +1,51 @@
|
||||
{
|
||||
"name": "deepforge",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/deepforge-dev/deepforge.git"
|
||||
},
|
||||
"bin": {
|
||||
"deepforge": "./bin/deepforge"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node app.js",
|
||||
"start": "./bin/deepforge start",
|
||||
"postinstall": "node utils/reinstall-extensions.js",
|
||||
"start-dev": "NODE_ENV=dev node app.js",
|
||||
"local": "node ./bin/start-local.js",
|
||||
"worker": "node ./bin/start-worker.js",
|
||||
"test": "node ./node_modules/mocha/bin/mocha --recursive test",
|
||||
"watch-test": "./node_modules/nodemon/bin/nodemon.js --exec 'node ./node_modules/mocha/bin/mocha --recursive test'",
|
||||
"test": "mkdir ./test-tmp; mocha --recursive test",
|
||||
"watch-test": "nodemon --exec 'mocha --recursive test'",
|
||||
"build-nn": "node ./utils/nn-parser.js"
|
||||
},
|
||||
"version": "0.8.0",
|
||||
"version": "1.4.1",
|
||||
"dependencies": {
|
||||
"commander": "^2.9.0",
|
||||
"dotenv": "^2.0.0",
|
||||
"exists-file": "^2.1.0",
|
||||
"express": "^4.14.0",
|
||||
"graceful-fs": "^4.1.10",
|
||||
"lodash.difference": "^4.1.2",
|
||||
"lodash.merge": "^4.5.1",
|
||||
"lodash.template": "^4.4.0",
|
||||
"mongodb": "^2.2.10",
|
||||
"nodemon": "^1.9.2",
|
||||
"webgme": "^2.0.0",
|
||||
"webgme-autoviz": "^2.0.3",
|
||||
"webgme-breadcrumbheader": "^2.1.0",
|
||||
"npm": "^4.0.5",
|
||||
"q": "1.4.1",
|
||||
"rimraf": "^2.4.0",
|
||||
"tcp-port-used": "^0.1.2",
|
||||
"webgme": "^2.7.1",
|
||||
"webgme-autoviz": "^2.2.0",
|
||||
"webgme-breadcrumbheader": "^2.1.1",
|
||||
"webgme-chflayout": "^2.0.0",
|
||||
"webgme-easydag": "dfst/webgme-easydag",
|
||||
"webgme-executor-worker": "^1.0.1",
|
||||
"webgme-fab": "dfst/webgme-fab",
|
||||
"webgme-simple-nodes": "^2.0.0"
|
||||
"webgme-simple-nodes": "^2.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"chai": "^3.0.0",
|
||||
"jszip": "^2.5.0",
|
||||
"mocha": "^2.2.5",
|
||||
"rimraf": "^2.4.0",
|
||||
"chai": "^3.0.0"
|
||||
"mockery": "^1.7.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
/* globals define */
|
||||
(function(root, factory){
|
||||
if(typeof define === 'function' && define.amd) {
|
||||
define([], function(){
|
||||
return factory();
|
||||
});
|
||||
} else if(typeof module === 'object' && module.exports) {
|
||||
module.exports = factory();
|
||||
} else {
|
||||
root.CONSTANTS = factory();
|
||||
}
|
||||
}(this, function() {
|
||||
return {
|
||||
CONTAINED_LAYER_SET: 'addLayers',
|
||||
CONTAINED_LAYER_INDEX: 'index',
|
||||
|
||||
LINE_OFFSET: 'lineOffset',
|
||||
DISPLAY_COLOR: 'displayColor',
|
||||
|
||||
// DeepForge metadata creation in dist execution
|
||||
START_CMD: 'deepforge-cmd',
|
||||
|
||||
IMAGE: { // all prefixed w/ 'IMG' for simple upload detection
|
||||
PREFIX: 'IMG',
|
||||
BASIC: 'IMG-B',
|
||||
CREATE: 'IMG-C',
|
||||
UPDATE: 'IMG-U',
|
||||
NAME: 'IMAGE-N' // No upload required
|
||||
},
|
||||
|
||||
GRAPH_CREATE: 'GRAPH',
|
||||
GRAPH_PLOT: 'PLOT',
|
||||
GRAPH_CREATE_LINE: 'LINE',
|
||||
GRAPH_LABEL_AXIS: {
|
||||
X: 'X',
|
||||
Y: 'Y'
|
||||
},
|
||||
|
||||
// Code Generation Constants
|
||||
CTOR_ARGS_ATTR: 'ctor_arg_order',
|
||||
|
||||
// Operation types
|
||||
OP: {
|
||||
INPUT: 'Input',
|
||||
OUTPUT: 'Output'
|
||||
},
|
||||
|
||||
// Heartbeat constants (ExecPulse router)
|
||||
PULSE: {
|
||||
DEAD: 0,
|
||||
ALIVE: 1,
|
||||
DOESNT_EXIST: 2
|
||||
},
|
||||
|
||||
// Job stdout update
|
||||
STDOUT_UPDATE: 'stdout_update'
|
||||
};
|
||||
}));
|
||||
@@ -0,0 +1,382 @@
|
||||
/* globals define*/
|
||||
(function(root, factory){
|
||||
if(typeof define === 'function' && define.amd) {
|
||||
define(['./lua'], function(luajs){
|
||||
return (root.LayerParser = factory(luajs));
|
||||
});
|
||||
} else if(typeof module === 'object' && module.exports) {
|
||||
var luajs = require('./lua');
|
||||
module.exports = (root.LayerParser = factory(luajs));
|
||||
}
|
||||
}(this, function(luajs) {
|
||||
var LayerParser = {};
|
||||
|
||||
//////////////////////// Setters ////////////////////////
|
||||
var returnsSelf = function(fnNode){
|
||||
var stats = fnNode.block.stats,
|
||||
last = stats[stats.length-1];
|
||||
|
||||
if (last.type === 'stat.return') {
|
||||
return last.nret[0].type === 'variable' && last.nret[0].val === 'self';
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
var isAttrSetter = function(node){
|
||||
if (node.type === 'stat.assignment' && node.lefts.length === 1) {
|
||||
var left = node.lefts[0];
|
||||
return left.type === 'expr.index' && left.self.val === 'self';
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
var getSettingAttrName = function(node){
|
||||
if (isAttrSetter(node)) {
|
||||
var left = node.lefts[0];
|
||||
return left.key.val;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
var getSettingAttrValue = function(node){
|
||||
if (isAttrSetter(node)) {
|
||||
return node.right;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
var isSetterMethod = function(curr, parent, className){
|
||||
if (parent && parent.type === 'stat.method') {
|
||||
// is it a fn w/ two statements (stats)
|
||||
if (parent.self.val === className && curr.type === 'function' &&
|
||||
curr.block.stats.length === 2) {
|
||||
// Is the first statement setting a value?
|
||||
return returnsSelf(curr) && getSettingAttrName(curr.block.stats[0]); // does it return itself?
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
var isFnArg = function(method, name) {
|
||||
return method.args.indexOf(name) !== -1;
|
||||
};
|
||||
|
||||
var getSetterSchema = function(node, method) {
|
||||
var setterType,
|
||||
setterFn,
|
||||
value = getSettingAttrValue(node);
|
||||
|
||||
if (value[0].type === 'variable' && isFnArg(method.func, value[0].val)) {
|
||||
setterType = 'arg';
|
||||
setterFn = method.key.val;
|
||||
} else {
|
||||
setterType = 'const';
|
||||
setterFn = {};
|
||||
setterFn[value[0].val] = method.key.val;
|
||||
}
|
||||
|
||||
return {
|
||||
setterType,
|
||||
setterFn
|
||||
};
|
||||
};
|
||||
|
||||
//////////////////////// Setters END ////////////////////////
|
||||
|
||||
var isInitFn = function(node, className) {
|
||||
if (node.type === 'stat.method' && node.self.val === className) {
|
||||
return node.key.val === '__init';
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
var getClassAttrDefs = function(method) {
|
||||
var fn = method.func,
|
||||
dict = {},
|
||||
attr,
|
||||
right,
|
||||
value;
|
||||
|
||||
luajs.codegen.traverse(curr => {
|
||||
if (isAttrSetter(curr)) {
|
||||
// Store the value if it is set to a constant
|
||||
attr = curr.lefts[0].key.val;
|
||||
right = curr.right[0];
|
||||
if (right.type.indexOf('const.') !== -1) {
|
||||
value = right.val;
|
||||
|
||||
if (right.type === 'const.nil') {
|
||||
value = null;
|
||||
}
|
||||
|
||||
dict[attr] = value;
|
||||
}
|
||||
}
|
||||
})(fn);
|
||||
|
||||
return dict;
|
||||
};
|
||||
|
||||
var getAttrsAndVals = function(method) {
|
||||
// Given a method, get the 'self' attributes and the default values
|
||||
var fn = method.func,
|
||||
dict = {},
|
||||
varName,
|
||||
value,
|
||||
varUsageCnt = {};
|
||||
|
||||
// Get the variables that are used only once (or updating themselves)
|
||||
luajs.codegen.traverse(curr => {
|
||||
if (curr.type === 'variable') {
|
||||
varUsageCnt[curr.val] = varUsageCnt[curr.val] ?
|
||||
varUsageCnt[curr.val] + 1 : 1;
|
||||
}
|
||||
})(method);
|
||||
|
||||
luajs.codegen.traverse(curr => {
|
||||
// If the variable is only used once and is 'or'-ed w/ a constant
|
||||
// during this use, we can infer that this is the default value
|
||||
if (curr.type === 'expr.op' && curr.op === 'op.or' &&
|
||||
curr.left.type === 'variable' && curr.right.type.indexOf('const') !== -1) {
|
||||
varName = curr.left.val;
|
||||
if (varUsageCnt[varName] === 1) {
|
||||
value = curr.right.type === 'const.nil' ? null : curr.right;
|
||||
dict[varName] = value;
|
||||
}
|
||||
}
|
||||
})(fn);
|
||||
|
||||
return dict;
|
||||
};
|
||||
|
||||
var copyNodeValues = function(attrs, from, to) {
|
||||
var value;
|
||||
for (var i = attrs.length; i--;) {
|
||||
value = from[attrs[i]] || null;
|
||||
if (value) {
|
||||
value = (value && value.hasOwnProperty('val')) ? value.val : value;
|
||||
to[attrs[i]] = value;
|
||||
}
|
||||
}
|
||||
return to;
|
||||
};
|
||||
|
||||
var getTypeCheckInfo = function(cond) {
|
||||
var caller,
|
||||
method,
|
||||
target,
|
||||
expType;
|
||||
|
||||
// Check for torch.isTypeOf:
|
||||
if (cond.type === 'expr.call' && cond.func.type === 'expr.index') {
|
||||
caller = cond.func.self.val;
|
||||
method = cond.func.key.val;
|
||||
|
||||
if (cond.type === 'expr.call' && caller === 'torch') {
|
||||
target = cond.args[0].val;
|
||||
if (method === 'isTypeOf' && target) {
|
||||
expType = cond.args[1].val;
|
||||
return {
|
||||
target,
|
||||
type: expType
|
||||
};
|
||||
}
|
||||
}
|
||||
} else if (cond.type === 'expr.op') { // torch.type() === ''
|
||||
// Check right side, too!
|
||||
var sides = [cond.left, cond.right],
|
||||
side,
|
||||
otherSide;
|
||||
|
||||
for (var i = sides.length; i--;) {
|
||||
side = sides[i];
|
||||
otherSide = sides[(i+1)%2];
|
||||
if (side.type === 'expr.call' && side.func.type === 'expr.index') {
|
||||
// Is it torch?
|
||||
caller = side.func.self.val;
|
||||
method = side.func.key.val;
|
||||
if (caller === 'torch' && method === 'type') {
|
||||
if (side.args[0].type === 'variable') {
|
||||
target = side.args[0].val;
|
||||
if (otherSide.type === 'const.string') {
|
||||
expType = otherSide.val;
|
||||
|
||||
return {
|
||||
target: target,
|
||||
type: expType
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
var isError = function(stat) {
|
||||
var fn;
|
||||
if (stat.type === 'stat.expr' && stat.expr.type === 'expr.call') {
|
||||
fn = stat.expr.func.val;
|
||||
return fn === 'error';
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
var inferParamTypes = function(node, paramDefs) {
|
||||
var types = {},
|
||||
check,
|
||||
cond;
|
||||
|
||||
// Infer from assertions
|
||||
luajs.codegen.traverse(curr => {
|
||||
// check for 'assert's that check type
|
||||
if (curr.type === 'expr.call' && curr.func.val === 'assert') {
|
||||
cond = curr.args[0];
|
||||
check = getTypeCheckInfo(cond);
|
||||
if (check) {
|
||||
types[check.target] = check.type;
|
||||
}
|
||||
} else if (curr.type === 'stat.if' && curr.cond.op === 'uop.not') {
|
||||
// if statements throwing errors on type mismatch
|
||||
cond = curr.cond.operand; // non-negated version
|
||||
// Check that it throws an error on true
|
||||
if (curr.tblock.stats.some(isError)) {
|
||||
check = getTypeCheckInfo(cond);
|
||||
if (check) {
|
||||
types[check.target] = check.type;
|
||||
}
|
||||
}
|
||||
}
|
||||
})(node);
|
||||
|
||||
// Infer from defaults
|
||||
Object.keys(paramDefs).forEach(param => {
|
||||
var val = paramDefs[param];
|
||||
if (val) { // initialized to 'null' doesn't help us...
|
||||
types[param] = val.type.replace('const.', '');
|
||||
}
|
||||
});
|
||||
|
||||
return types;
|
||||
};
|
||||
|
||||
var findTorchClass = function(ast){
|
||||
var torchClassArgs, // args for `torch.class(...)`
|
||||
name = '',
|
||||
alias,
|
||||
baseType,
|
||||
params,
|
||||
setters = {},
|
||||
defaults = {},
|
||||
paramDefs,
|
||||
attrDefs;
|
||||
|
||||
if(ast.type == 'function'){
|
||||
ast.block.stats.forEach(function(func){
|
||||
if(func.type == 'stat.local' && func.right && func.right[0] &&
|
||||
func.right[0].func && func.right[0].func.self &&
|
||||
func.right[0].func.self.val == 'torch' &&
|
||||
func.right[0].func.key.val == 'class'){
|
||||
|
||||
torchClassArgs = func.right[0].args.map(arg => arg.val);
|
||||
name = torchClassArgs[0];
|
||||
if(name !== ''){
|
||||
name = name.replace('nn.', '');
|
||||
alias = func.names[0] || name;
|
||||
if (torchClassArgs.length > 1) {
|
||||
baseType = torchClassArgs[1].replace('nn.', '');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Get the setters, defaults and type info (inferred)
|
||||
var setterNames,
|
||||
schema,
|
||||
types,
|
||||
values;
|
||||
|
||||
luajs.codegen.traverse((curr, parent) => {
|
||||
var firstLine,
|
||||
attrName;
|
||||
|
||||
// Record the setter functions
|
||||
if (isSetterMethod(curr, parent, alias)) {
|
||||
firstLine = curr.block.stats[0];
|
||||
// just use the attribute attrName for now...
|
||||
attrName = getSettingAttrName(firstLine);
|
||||
|
||||
// merge schemas
|
||||
schema = getSetterSchema(firstLine, parent);
|
||||
if (setters[attrName] && setters[attrName].setterType === 'const') { // merge
|
||||
for (var val in schema.setterFn) {
|
||||
setters[attrName].setterFn[val] = schema.setterFn[val];
|
||||
}
|
||||
} else {
|
||||
setters[attrName] = schema;
|
||||
}
|
||||
} else if (isInitFn(curr, alias)) { // Record the defaults
|
||||
paramDefs = getAttrsAndVals(curr);
|
||||
attrDefs = getClassAttrDefs(curr);
|
||||
types = inferParamTypes(curr, paramDefs);
|
||||
|
||||
// get ctor args
|
||||
params = curr.func.args;
|
||||
if(params.length === 0 && curr.func.varargs){
|
||||
params.push('params');
|
||||
}
|
||||
}
|
||||
|
||||
})(ast);
|
||||
|
||||
// Get the defaults for the params from defs
|
||||
if (paramDefs && params) {
|
||||
copyNodeValues(params, paramDefs, defaults);
|
||||
}
|
||||
|
||||
// Get the defaults for the setters from attrDefs
|
||||
if (attrDefs) {
|
||||
setterNames = Object.keys(setters);
|
||||
copyNodeValues(setterNames, attrDefs, defaults);
|
||||
}
|
||||
|
||||
// Remove any const setters w/ only one value and no default
|
||||
setterNames = Object.keys(setters);
|
||||
for (var i = setterNames.length; i--;) {
|
||||
schema = setters[setterNames[i]];
|
||||
if (schema.setterType === 'const') {
|
||||
values = Object.keys(schema.setterFn);
|
||||
if (values.length === 1 &&
|
||||
// boolean setters can have the default value inferred
|
||||
values[0] !== 'true' && values[0] !== 'false' &&
|
||||
!defaults[setterNames[i]]) {
|
||||
|
||||
delete setters[setterNames[i]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
name,
|
||||
baseType,
|
||||
params,
|
||||
setters,
|
||||
types,
|
||||
defaults
|
||||
};
|
||||
};
|
||||
|
||||
LayerParser.parse = function(text) {
|
||||
try {
|
||||
var ast = luajs.parser.parse(text);
|
||||
return findTorchClass(ast);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
return LayerParser;
|
||||
}));
|
||||
@@ -0,0 +1,75 @@
|
||||
/*globals define*/
|
||||
define([
|
||||
'q',
|
||||
'superagent'
|
||||
], function(
|
||||
Q,
|
||||
superagent
|
||||
) {
|
||||
'use strict';
|
||||
|
||||
// Wrap the ability to read, update, and delete logs using the JobLogsAPI
|
||||
var APIClient = function(params) {
|
||||
params = params || {};
|
||||
|
||||
this.logger = this.logger || params.logger.fork('APIClient');
|
||||
|
||||
// Get the server url
|
||||
this.token = params.token;
|
||||
this.origin = this._getServerUrl(params);
|
||||
this.relativeUrl = this.relativeUrl || '';
|
||||
this.url = this.origin + this.relativeUrl;
|
||||
|
||||
this.logger.debug(`Setting url to ${this.url}`);
|
||||
|
||||
this.branch = params.branchName;
|
||||
this.project = params.projectId;
|
||||
this._modifiedJobs = [];
|
||||
|
||||
this.logger.debug(`Using <project>:<branch>: "${this.project}"/"${this.branch}"`);
|
||||
this.logger.info('ctor finished');
|
||||
};
|
||||
|
||||
APIClient.prototype._getServerUrl = function(params) {
|
||||
if (typeof window !== 'undefined') {
|
||||
return window.location.origin;
|
||||
}
|
||||
|
||||
// If not in browser, set using the params
|
||||
var server = params.server || '127.0.0.1',
|
||||
port = params.port || '80',
|
||||
protocol = params.httpsecure ? 'https' : 'http'; // default is http
|
||||
|
||||
return params.origin || `${protocol}://${server}:${port}`;
|
||||
};
|
||||
|
||||
APIClient.prototype.getUrl = function() {
|
||||
return this.url;
|
||||
};
|
||||
|
||||
APIClient.prototype._request = function(method, jobId, content) {
|
||||
var deferred = Q.defer(),
|
||||
req = superagent[method](this.getUrl(jobId));
|
||||
|
||||
this.logger.debug(`sending ${method} request to ${this.getUrl(jobId)}`);
|
||||
if (this.token) {
|
||||
req.set('Authorization', 'Bearer ' + this.token);
|
||||
}
|
||||
|
||||
if (content) {
|
||||
req = req.send(content);
|
||||
}
|
||||
|
||||
req.end((err, res) => {
|
||||
if (err || res.status > 399) {
|
||||
return deferred.reject(res || err);
|
||||
}
|
||||
|
||||
return deferred.resolve(res);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
return APIClient;
|
||||
});
|
||||
@@ -0,0 +1,44 @@
|
||||
/* globals define */
|
||||
define([
|
||||
'./APIClient'
|
||||
], function(
|
||||
APIClient
|
||||
) {
|
||||
'use strict';
|
||||
|
||||
var ExecPulseClient = function(params) {
|
||||
this.relativeUrl = '/execution/pulse/';
|
||||
this.logger = params.logger.fork('ExecPulseClient');
|
||||
APIClient.call(this, params);
|
||||
};
|
||||
|
||||
ExecPulseClient.prototype = Object.create(APIClient.prototype);
|
||||
|
||||
ExecPulseClient.prototype.getUrl = function(hash) {
|
||||
return this.url + hash;
|
||||
};
|
||||
|
||||
// - update the heartbeat
|
||||
// - check the heartbeat
|
||||
// - delete the heartbeat
|
||||
ExecPulseClient.prototype.update = function(hash) {
|
||||
return this._request('post', hash)
|
||||
.catch(err => {
|
||||
throw err.text || err;
|
||||
});
|
||||
};
|
||||
|
||||
ExecPulseClient.prototype.check = function(hash) {
|
||||
return this._request('get', hash)
|
||||
.then(res => JSON.parse(res.text))
|
||||
.catch(err => {
|
||||
throw err.text || err;
|
||||
});
|
||||
};
|
||||
|
||||
ExecPulseClient.prototype.clear = function(hash) {
|
||||
return this._request('delete', hash);
|
||||
};
|
||||
|
||||
return ExecPulseClient;
|
||||
});
|
||||
@@ -0,0 +1,120 @@
|
||||
/* globals define */
|
||||
define([
|
||||
'./APIClient',
|
||||
'q',
|
||||
'superagent'
|
||||
], function(
|
||||
APIClient,
|
||||
Q,
|
||||
superagent
|
||||
) {
|
||||
'use strict';
|
||||
|
||||
// Wrap the ability to read, update, and delete logs using the JobLogsAPI
|
||||
var METADATA_FIELDS = [
|
||||
'lineCount'
|
||||
];
|
||||
var JobLogsClient = function(params) {
|
||||
params = params || {};
|
||||
|
||||
this.relativeUrl = '/execution/logs';
|
||||
this.logger = params.logger.fork('JobLogsClient');
|
||||
APIClient.call(this, params);
|
||||
|
||||
// Get the project, branch name
|
||||
if (!(params.branchName && params.projectId)) {
|
||||
throw Error('"branchName" and "projectId" required');
|
||||
}
|
||||
|
||||
this._modifiedJobs = [];
|
||||
|
||||
this.logger.debug(`Using <project>:<branch>: "${this.project}"/"${this.branch}"`);
|
||||
this.logger.info('ctor finished');
|
||||
};
|
||||
|
||||
JobLogsClient.prototype = Object.create(APIClient.prototype);
|
||||
|
||||
// This method could be optimized - it could make a log of requests
|
||||
JobLogsClient.prototype.fork = function(forkName) {
|
||||
var jobIds = this._modifiedJobs,
|
||||
deferred = Q.defer(),
|
||||
url = [
|
||||
this.url,
|
||||
'migrate',
|
||||
encodeURIComponent(this.project),
|
||||
encodeURIComponent(this.branch),
|
||||
encodeURIComponent(forkName)
|
||||
].join('/'),
|
||||
req = superagent.post(url);
|
||||
|
||||
this.logger.info(`migrating ${jobIds.length} jobs from ${this.branch} to ${forkName} in ${this.project}`);
|
||||
if (this.token) {
|
||||
req.set('Authorization', 'Bearer ' + this.token);
|
||||
}
|
||||
|
||||
req.send({jobs: jobIds})
|
||||
.end((err, res) => {
|
||||
if (err || res.status > 399) {
|
||||
return deferred.reject(err || res.status);
|
||||
}
|
||||
|
||||
return deferred.resolve(res);
|
||||
});
|
||||
|
||||
this.branch = forkName;
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
JobLogsClient.prototype.getUrl = function(jobId) {
|
||||
var url = this.url;
|
||||
|
||||
if (typeof jobId !== 'string') {
|
||||
url = this.url + jobId.route;
|
||||
jobId = jobId.jobId;
|
||||
}
|
||||
|
||||
return [
|
||||
url,
|
||||
encodeURIComponent(this.project),
|
||||
encodeURIComponent(this.branch),
|
||||
encodeURIComponent(jobId)
|
||||
].join('/');
|
||||
};
|
||||
|
||||
var hasRequiredFields = function(md) {
|
||||
return METADATA_FIELDS.reduce((passing, nextField) => {
|
||||
return passing && md.hasOwnProperty(nextField);
|
||||
}, true);
|
||||
};
|
||||
|
||||
JobLogsClient.prototype.appendTo = function(jobId, text, metadata) {
|
||||
this._modifiedJobs.push(jobId);
|
||||
this.logger.info(`Appending logs to ${jobId}`);
|
||||
|
||||
if (metadata && !hasRequiredFields(metadata)) {
|
||||
throw Error(`Required metadata fields: ${METADATA_FIELDS.join(', ')}`);
|
||||
}
|
||||
metadata = metadata || {};
|
||||
metadata.patch = text;
|
||||
return this._request('patch', jobId, metadata);
|
||||
};
|
||||
|
||||
JobLogsClient.prototype.getLog = function(jobId) {
|
||||
this.logger.info(`Getting logs for ${jobId}`);
|
||||
return this._request('get', jobId)
|
||||
.then(res => res.text);
|
||||
};
|
||||
|
||||
JobLogsClient.prototype.deleteLog = function(jobId) {
|
||||
this.logger.info(`Deleting logs for ${jobId}`);
|
||||
return this._request('delete', jobId);
|
||||
};
|
||||
|
||||
JobLogsClient.prototype.getMetadata = function(jobId) {
|
||||
this.logger.info(`Getting line count for ${jobId}`);
|
||||
return this._request('get', {jobId: jobId, route: '/metadata'})
|
||||
.then(res => JSON.parse(res.text));
|
||||
};
|
||||
|
||||
return JobLogsClient;
|
||||
});
|
||||
@@ -0,0 +1,60 @@
|
||||
/* globals define */
|
||||
define([
|
||||
'./APIClient'
|
||||
], function(
|
||||
APIClient
|
||||
) {
|
||||
'use strict';
|
||||
|
||||
var JobOriginClient = function(params) {
|
||||
this.relativeUrl = '/job/origins/';
|
||||
this.logger = params.logger.fork('JobOriginClient');
|
||||
APIClient.call(this, params);
|
||||
};
|
||||
|
||||
JobOriginClient.prototype = Object.create(APIClient.prototype);
|
||||
|
||||
// - Record the origin
|
||||
// - Look up the origin
|
||||
// - Delete record
|
||||
JobOriginClient.prototype.getUrl = function(hash) {
|
||||
return this.url + hash;
|
||||
};
|
||||
|
||||
JobOriginClient.prototype.record = function(hash, info) {
|
||||
var jobInfo = {
|
||||
hash: hash,
|
||||
nodeId: info.nodeId,
|
||||
job: info.job,
|
||||
project: info.project || this.project,
|
||||
branch: info.branch || this.branch,
|
||||
execution: info.execution
|
||||
};
|
||||
|
||||
return this._request('post', hash, jobInfo)
|
||||
.catch(err => {
|
||||
throw err.text || err;
|
||||
});
|
||||
};
|
||||
|
||||
JobOriginClient.prototype.getOrigin = function(hash) {
|
||||
return this._request('get', hash)
|
||||
.then(res => JSON.parse(res.text))
|
||||
.catch(res => {
|
||||
if (res.status && res.status === 404) {
|
||||
return null;
|
||||
}
|
||||
throw res;
|
||||
});
|
||||
};
|
||||
|
||||
JobOriginClient.prototype.fork = function(hash, forkName) {
|
||||
return this._request('patch', hash, {branch: forkName});
|
||||
};
|
||||
|
||||
JobOriginClient.prototype.deleteRecord = function(hash) {
|
||||
return this._request('delete', hash);
|
||||
};
|
||||
|
||||
return JobOriginClient;
|
||||
});
|
||||
@@ -1,13 +1,17 @@
|
||||
/* globals Materialize, WebGMEGlobal, define*/
|
||||
/* globals WebGMEGlobal, define*/
|
||||
// This file creates the DeepForge namespace and defines basic actions
|
||||
define([
|
||||
'panel/FloatingActionButton/styles/Materialize',
|
||||
'js/RegistryKeys',
|
||||
'js/Panels/MetaEditor/MetaEditorConstants',
|
||||
'js/Constants'
|
||||
'js/Constants',
|
||||
'q'
|
||||
], function(
|
||||
Materialize,
|
||||
REGISTRY_KEYS,
|
||||
META_CONSTANTS,
|
||||
CONSTANTS
|
||||
CONSTANTS,
|
||||
Q
|
||||
) {
|
||||
var DeepForge = {},
|
||||
placesTerritoryId,
|
||||
@@ -53,7 +57,7 @@ define([
|
||||
};
|
||||
|
||||
var createNamedNode = function(baseId, parentId, isMeta) {
|
||||
var newId = client.createChild({parentId, baseId}),
|
||||
var newId = client.createNode({parentId, baseId}),
|
||||
baseNode = client.getNode(baseId),
|
||||
basename = 'New' + baseNode.getAttribute('name'),
|
||||
newName = getUniqueName(parentId, basename);
|
||||
@@ -68,7 +72,7 @@ define([
|
||||
client.setRegistry(newId, 'isAbstract', false);
|
||||
}
|
||||
|
||||
client.setAttributes(newId, 'name', newName);
|
||||
client.setAttribute(newId, 'name', newName);
|
||||
return newId;
|
||||
};
|
||||
|
||||
@@ -79,7 +83,9 @@ define([
|
||||
exists = {},
|
||||
i = 2;
|
||||
|
||||
children.forEach(child => exists[child.getAttribute('name')] = true);
|
||||
children
|
||||
.filter(child => child !== null)
|
||||
.forEach(child => exists[child.getAttribute('name')] = true);
|
||||
|
||||
while (exists[name]) {
|
||||
name = basename + '_' + i;
|
||||
@@ -90,6 +96,7 @@ define([
|
||||
};
|
||||
|
||||
//////////////////// DeepForge places detection ////////////////////
|
||||
DeepForge.places = {};
|
||||
var TYPE_TO_CONTAINER = {
|
||||
|
||||
Architecture: 'MyArchitectures',
|
||||
@@ -105,10 +112,29 @@ define([
|
||||
PLACE_NAMES = Object.keys(TYPE_TO_CONTAINER).map(key => TYPE_TO_CONTAINER[key]);
|
||||
|
||||
// Add DeepForge directories
|
||||
var placePromises = {},
|
||||
setPlaceId = {},
|
||||
firstProject = true;
|
||||
|
||||
var getPlace = function(name) {
|
||||
return placePromises[name];
|
||||
};
|
||||
|
||||
var initializePlaces = function() {
|
||||
PLACE_NAMES.forEach(name => {
|
||||
var deferred = Q.defer();
|
||||
placePromises[name] = deferred.promise;
|
||||
setPlaceId[name] = deferred.resolve;
|
||||
});
|
||||
};
|
||||
|
||||
var updateDeepForgeNamespace = function() {
|
||||
var territory = {};
|
||||
|
||||
DeepForge.places = {};
|
||||
if (!firstProject) {
|
||||
initializePlaces();
|
||||
}
|
||||
firstProject = false;
|
||||
|
||||
// Create a territory
|
||||
if (placesTerritoryId) {
|
||||
@@ -135,14 +161,16 @@ define([
|
||||
nodes.forEach(node =>
|
||||
nodeIdsByName[node.getAttribute('name')] = node.getId());
|
||||
|
||||
PLACE_NAMES.forEach(name => DeepForge.places[name] = nodeIdsByName[name]);
|
||||
PLACE_NAMES.forEach(name => setPlaceId[name](nodeIdsByName[name]));
|
||||
|
||||
// Remove the territory
|
||||
client.removeUI(placesTerritoryId);
|
||||
placesTerritoryId = null;
|
||||
initializePrevLocations();
|
||||
};
|
||||
|
||||
initializePlaces();
|
||||
PLACE_NAMES.forEach(name => DeepForge.places[name] = getPlace.bind(null, name));
|
||||
|
||||
//////////////////// DeepForge creation actions ////////////////////
|
||||
var instances = [
|
||||
'Architecture',
|
||||
@@ -155,8 +183,7 @@ define([
|
||||
];
|
||||
|
||||
var createNew = function(type, metasheetName) {
|
||||
var parentId,
|
||||
placeName = TYPE_TO_CONTAINER[type],
|
||||
var placeName = TYPE_TO_CONTAINER[type],
|
||||
newId,
|
||||
baseId,
|
||||
msg = `Created new ${type + (metasheetName ? ' prototype' : '')}`;
|
||||
@@ -166,19 +193,20 @@ define([
|
||||
.getId();
|
||||
|
||||
// Look up the parent container
|
||||
parentId = DeepForge.places[placeName];
|
||||
return DeepForge.places[placeName]().then(parentId => {
|
||||
|
||||
client.startTransaction(msg);
|
||||
newId = createNamedNode(baseId, parentId, !!metasheetName);
|
||||
client.startTransaction(msg);
|
||||
newId = createNamedNode(baseId, parentId, !!metasheetName);
|
||||
|
||||
if (metasheetName) {
|
||||
addToMetaSheet(newId, metasheetName);
|
||||
}
|
||||
if (metasheetName) {
|
||||
addToMetaSheet(newId, metasheetName);
|
||||
}
|
||||
|
||||
client.completeTransaction();
|
||||
client.completeTransaction();
|
||||
|
||||
WebGMEGlobal.State.registerActiveObject(newId);
|
||||
return newId;
|
||||
WebGMEGlobal.State.registerActiveObject(newId);
|
||||
return newId;
|
||||
});
|
||||
};
|
||||
|
||||
var createCustomLayer = function(typeName) {
|
||||
@@ -199,16 +227,20 @@ define([
|
||||
}
|
||||
}
|
||||
|
||||
client.startTransaction(msg);
|
||||
return DeepForge.places.MyLayers()
|
||||
.then(id => {
|
||||
|
||||
newId = createNamedNode(baseId, DeepForge.places.MyLayers, true);
|
||||
addToMetaSheet(newId, 'CustomLayers');
|
||||
client.addMixin(newId, customLayerId);
|
||||
client.setRegistry(newId, REGISTRY_KEYS.IS_ABSTRACT, false);
|
||||
client.startTransaction(msg);
|
||||
|
||||
client.completeTransaction();
|
||||
newId = createNamedNode(baseId, id, true);
|
||||
addToMetaSheet(newId, 'CustomLayers');
|
||||
client.addMixin(newId, customLayerId);
|
||||
client.setRegistry(newId, REGISTRY_KEYS.IS_ABSTRACT, false);
|
||||
|
||||
WebGMEGlobal.State.registerActiveObject(newId);
|
||||
client.completeTransaction();
|
||||
|
||||
WebGMEGlobal.State.registerActiveObject(newId);
|
||||
});
|
||||
};
|
||||
|
||||
// Creating Artifacts
|
||||
@@ -235,14 +267,12 @@ define([
|
||||
}
|
||||
|
||||
dataBaseId = dataBase.getId();
|
||||
dataTypes = metanodes.filter(n => client.isTypeOf(n.getId(), dataBaseId))
|
||||
dataTypes = metanodes.filter(n => n.isTypeOf(dataBaseId))
|
||||
.filter(n => !n.getRegistry('isAbstract'))
|
||||
.map(node => node.getAttribute('name'));
|
||||
|
||||
//this.logger.info(`Found ${dataTypes.length} data types`);
|
||||
|
||||
// Add the target type to the pluginMetadata... hacky :/
|
||||
var metadata = WebGMEGlobal.allPluginsMetadata[UPLOAD_PLUGIN],
|
||||
// Add the target type to the pluginMetadata...
|
||||
var metadata = WebGMEGlobal.allPluginsMetadata[UPLOAD_PLUGIN],
|
||||
config = metadata.configStructure
|
||||
.find(opt => opt.name === DATA_TYPE_CONFIG.name);
|
||||
|
||||
@@ -266,7 +296,9 @@ define([
|
||||
});
|
||||
};
|
||||
|
||||
DeepForge.create = {};
|
||||
DeepForge.last = {};
|
||||
DeepForge.create = {};
|
||||
DeepForge.register = {};
|
||||
instances.forEach(type => {
|
||||
DeepForge.create[type] = function() {
|
||||
return createNew.call(null, type);
|
||||
@@ -277,19 +309,16 @@ define([
|
||||
DeepForge.create[type] = function() {
|
||||
return createNew.call(null, type, type);
|
||||
};
|
||||
DeepForge.register[type] = function(id) {
|
||||
// Add the given element to the metasheet!
|
||||
return addToMetaSheet(id, type);
|
||||
};
|
||||
});
|
||||
|
||||
DeepForge.create.Layer = createCustomLayer;
|
||||
DeepForge.create.Artifact = uploadArtifact;
|
||||
|
||||
//////////////////// DeepForge prev locations ////////////////////
|
||||
var initializePrevLocations = function() {
|
||||
DeepForge.last = {};
|
||||
Object.keys(TYPE_TO_CONTAINER).forEach(type =>
|
||||
DeepForge.last[type] = DeepForge.places[TYPE_TO_CONTAINER[type]]
|
||||
);
|
||||
};
|
||||
|
||||
// Update DeepForge on project changed
|
||||
WebGMEGlobal.State.on('change:' + CONSTANTS.STATE_ACTIVE_PROJECT_NAME,
|
||||
updateDeepForgeNamespace, null);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
/* globals define*/
|
||||
define([
|
||||
'deepforge/Constants'
|
||||
], function(
|
||||
Constants
|
||||
) {
|
||||
'use strict';
|
||||
|
||||
@@ -15,32 +17,49 @@ define([
|
||||
return result;
|
||||
};
|
||||
|
||||
var isArgument = function(arg) {
|
||||
return arg.hasOwnProperty('argindex');
|
||||
};
|
||||
|
||||
var sortByIndex = function(a, b) {
|
||||
return a.argindex > b.argindex;
|
||||
var isSetter = function(arg) {
|
||||
return arg.hasOwnProperty('setterType');
|
||||
};
|
||||
|
||||
var createLayerDict = function(core, meta) {
|
||||
var node,
|
||||
names = Object.keys(meta),
|
||||
layers = {};
|
||||
layers = {},
|
||||
setters,
|
||||
ctorData,
|
||||
ctorArgs,
|
||||
attrs;
|
||||
|
||||
for (var i = names.length; i--;) {
|
||||
node = meta[names[i]];
|
||||
layers[names[i]] = core.getValidAttributeNames(node)
|
||||
ctorData = core.getAttribute(node, Constants.CTOR_ARGS_ATTR);
|
||||
attrs = core.getValidAttributeNames(node);
|
||||
|
||||
layers[names[i]] = {};
|
||||
if (ctorData) {
|
||||
ctorArgs = ctorData.split(',')
|
||||
.map(attr => prepAttribute(core, node, attr));
|
||||
|
||||
// Get the constructor args
|
||||
layers[names[i]].args = ctorArgs;
|
||||
} else {
|
||||
layers[names[i]].args = [];
|
||||
}
|
||||
|
||||
layers[names[i]].setters = {};
|
||||
setters = attrs
|
||||
.map(attr => prepAttribute(core, node, attr))
|
||||
.filter(isArgument)
|
||||
.sort(sortByIndex);
|
||||
.filter(isSetter);
|
||||
for (var j = setters.length; j--;) {
|
||||
layers[names[i]].setters[setters[j].name] = setters[j];
|
||||
}
|
||||
}
|
||||
|
||||
return layers;
|
||||
};
|
||||
|
||||
// When provided with the META, create the given LayerDict object
|
||||
// - Sort (and filter) by argindex
|
||||
// - Filter out the ctor args (in order)
|
||||
// - add name attribute to schema
|
||||
// - store this array under the META name
|
||||
|
||||
|
||||
@@ -1,546 +0,0 @@
|
||||
# This file should actually be an alternative way of viewing the metamodel.
|
||||
#
|
||||
# This contains metadata about the Torch nn library used for
|
||||
# creating the metamodel
|
||||
#
|
||||
# By default...
|
||||
# - all attributes are a number
|
||||
# - default values are optional
|
||||
# - all booleans default to false
|
||||
# - list attributes are specified with WORD...WORD
|
||||
# - if `ignore` is set, the attribute is not added to the metamodel
|
||||
|
||||
# This should have tests to verify that this document is up to date...
|
||||
# TODO
|
||||
|
||||
Containers:
|
||||
- Concat:
|
||||
- dim:
|
||||
min: 1 # TODO: Figure out exactly how this works
|
||||
|
||||
Module:
|
||||
- SpatialBatchNormalization:
|
||||
- input:
|
||||
infer: dimensionality # change this to `infer: 'dimensionality'`
|
||||
- eps:
|
||||
default: 0.00001
|
||||
- momentum:
|
||||
default: 0.1
|
||||
- affine:
|
||||
default: true
|
||||
|
||||
- BatchNormalization:
|
||||
- input:
|
||||
infer: dimensionality # change this to `infer: 'dimensionality'`
|
||||
- eps:
|
||||
default: 0.00001
|
||||
- momentum:
|
||||
default: 0.1
|
||||
- affine:
|
||||
default: true
|
||||
|
||||
- Threshold:
|
||||
- threshold:
|
||||
type: float
|
||||
default: 1e-6
|
||||
- value:
|
||||
type: float
|
||||
default: 0
|
||||
- inplace:
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
ConvLayer:
|
||||
- TemporalConvolution:
|
||||
- inputFrameSize:
|
||||
min: 1
|
||||
- outputFrameSize:
|
||||
min: 1
|
||||
- kernelWidth:
|
||||
min: 1
|
||||
- step:
|
||||
default: 1
|
||||
|
||||
- TemporalMaxPooling:
|
||||
- kernelWidth:
|
||||
min: 1
|
||||
- step: # FIXME: defaults to 'kernelWidth'
|
||||
min: 1
|
||||
|
||||
- TemporalSubSampling:
|
||||
- inputFrameSize:
|
||||
min: 1
|
||||
- kernelWidth:
|
||||
min: 1
|
||||
- step:
|
||||
min: 1
|
||||
# TODO: What is the default?
|
||||
|
||||
- LookupTable:
|
||||
- nIndex:
|
||||
min: 1
|
||||
- sizes:
|
||||
min: 1
|
||||
|
||||
# Spatial Modules
|
||||
- SpatialConvolutionMM:
|
||||
- nInputPlane: # TODO: Infer this
|
||||
min: 1
|
||||
- nOutputPlane:
|
||||
min: 1
|
||||
- kernelWidth:
|
||||
min: 1
|
||||
- kernelHeight:
|
||||
min: 1
|
||||
|
||||
- strideWidth:
|
||||
min: 1
|
||||
default: 1
|
||||
- strideHeight:
|
||||
min: 1
|
||||
default: 1
|
||||
- padWidth:
|
||||
min: 0
|
||||
default: 0
|
||||
- padHeight: # FIXME: this defaults to padWidth - not 0
|
||||
min: 0
|
||||
default: 0
|
||||
|
||||
- SpatialConvolution:
|
||||
- nInputPlane:
|
||||
min: 1
|
||||
- nOutputPlane:
|
||||
min: 1
|
||||
- kernelWidth:
|
||||
min: 1
|
||||
- kernelHeight:
|
||||
min: 1
|
||||
- strideWidth:
|
||||
min: 1
|
||||
default: 1
|
||||
- strideHeight:
|
||||
min: 1
|
||||
default: 1
|
||||
|
||||
- SpatialConvolutionMap:
|
||||
- connectionMatrix:
|
||||
min: 1
|
||||
- kernelWidth:
|
||||
min: 1
|
||||
- kernelHeight:
|
||||
min: 1
|
||||
- strideWidth:
|
||||
min: 1
|
||||
default: 1
|
||||
- strideHeight:
|
||||
min: 1
|
||||
default: 1
|
||||
|
||||
- SpatialLPPooling:
|
||||
- nInputPlane:
|
||||
min: 1
|
||||
- norm:
|
||||
min: 1
|
||||
- kernelWidth:
|
||||
min: 1
|
||||
- kernelHeight:
|
||||
min: 1
|
||||
- strideWidth:
|
||||
min: 1
|
||||
default: 1
|
||||
- strideHeight:
|
||||
min: 1
|
||||
default: 1
|
||||
|
||||
- SpatialMaxPooling:
|
||||
- kernelWidth:
|
||||
min: 1
|
||||
- kernelHeight:
|
||||
min: 1
|
||||
- strideWidth:
|
||||
min: 1
|
||||
default: 1
|
||||
- strideHeight:
|
||||
min: 1
|
||||
default: 1
|
||||
|
||||
- SpatialAveragePooling:
|
||||
- kernelWidth:
|
||||
min: 1
|
||||
- kernelHeight:
|
||||
min: 1
|
||||
- strideWidth:
|
||||
min: 1
|
||||
default: 1
|
||||
- strideHeight:
|
||||
min: 1
|
||||
default: 1
|
||||
|
||||
- SpatialAdaptiveMaxPooling: # output is width x height
|
||||
- width:
|
||||
min: 1
|
||||
- height:
|
||||
min: 1
|
||||
|
||||
- SpatialSubSampling:
|
||||
- nInputPlane:
|
||||
min: 1
|
||||
- kernelWidth:
|
||||
min: 1
|
||||
- kernelHeight:
|
||||
min: 1
|
||||
- strideWidth:
|
||||
min: 1
|
||||
default: 1
|
||||
- strideHeight:
|
||||
min: 1
|
||||
default: 1
|
||||
|
||||
- SpatialUpSamplingNearest:
|
||||
- scale: # upscale ratio
|
||||
min: 1
|
||||
|
||||
- SpatialZeroPadding:
|
||||
- left:
|
||||
min: 0
|
||||
- right:
|
||||
min: 0
|
||||
- top:
|
||||
min: 0
|
||||
- bottom:
|
||||
min: 0
|
||||
|
||||
- SpatialSubtractiveNormalization:
|
||||
- nInputPlane:
|
||||
min: 1
|
||||
- kernel:
|
||||
min: 1
|
||||
|
||||
- SpatialCrossMapLRN:
|
||||
- size:
|
||||
min: 1
|
||||
- alpha:
|
||||
default: 0.0001
|
||||
- beta:
|
||||
default: 0.75
|
||||
- k:
|
||||
default: 1
|
||||
|
||||
- SpatialConvolutionLocal:
|
||||
- nInputPlane:
|
||||
min: 1
|
||||
- nOutputPlane:
|
||||
min: 1
|
||||
- inputWidth: # TODO: infer this
|
||||
min: 1
|
||||
- inputHeight: # TODO: infer this
|
||||
min: 1
|
||||
- kernelWidth:
|
||||
min: 1
|
||||
- kernelHeight:
|
||||
min: 1
|
||||
- strideWidth:
|
||||
min: 1
|
||||
default: 1
|
||||
- strideHeight:
|
||||
min: 1
|
||||
default: 1
|
||||
- padWidth:
|
||||
min: 0
|
||||
default: 0
|
||||
- padHeight: # FIXME: this defaults to padWidth - not 0
|
||||
min: 0
|
||||
default: 0
|
||||
|
||||
- SpatialDropout:
|
||||
- probability:
|
||||
default: 0.5
|
||||
|
||||
- SpatialFractionalMaxPooling:
|
||||
- poolWidth:
|
||||
- min: 2
|
||||
- poolHeight:
|
||||
- min: 2
|
||||
- outWidth: # Optionally, these could be ratioW/H FIXME
|
||||
- min: 1
|
||||
- outHeight:
|
||||
- min: 1
|
||||
|
||||
- SpatialDivisiveNormalization:
|
||||
- nInputPlane: # TODO: infer this
|
||||
- default: 1
|
||||
- kernel: # TODO: this is a tensor type...
|
||||
- threshold:
|
||||
- default: 0.0001
|
||||
- thresval:
|
||||
- default: 0.0001 # FIXME: this defaults to "threshold"
|
||||
|
||||
- SpatialContrastiveNormalization:
|
||||
- nInputPlane: # TODO: infer this
|
||||
- default: 1
|
||||
- kernel: # TODO: this is a tensor type...
|
||||
- threshold:
|
||||
- default: 0.0001
|
||||
- thresval:
|
||||
- default: 0.0001 # FIXME: this defaults to "threshold"
|
||||
|
||||
- SpatialFullConvolution:
|
||||
- nInputPlane: # TODO: should infer this
|
||||
min: 1
|
||||
- nOutputPlane:
|
||||
min: 1
|
||||
- kernelWidth:
|
||||
min: 1
|
||||
- kernelHeight:
|
||||
min: 1
|
||||
- strideWidth:
|
||||
min: 1
|
||||
default: 1
|
||||
- strideHeight:
|
||||
min: 1
|
||||
default: 1
|
||||
- padWidth:
|
||||
min: 0
|
||||
default: 0
|
||||
- padHeight:
|
||||
min: 0
|
||||
default: 0
|
||||
- adjWidth:
|
||||
min: 0
|
||||
default: 0
|
||||
- adjHeight:
|
||||
min: 0
|
||||
default: 0
|
||||
# Additional constraint:
|
||||
|
||||
# Volumetric Modules
|
||||
- VolumetricConvolution:
|
||||
- nInputPlane:
|
||||
min: 1
|
||||
- nOutputPlane:
|
||||
min: 1
|
||||
- kernelTime:
|
||||
min: 1
|
||||
- kernelWidth:
|
||||
min: 1
|
||||
- kernelHeight:
|
||||
min: 1
|
||||
- strideTime:
|
||||
min: 1
|
||||
default: 1
|
||||
- strideWidth:
|
||||
min: 1
|
||||
default: 1
|
||||
- strideHeight:
|
||||
min: 1
|
||||
default: 1
|
||||
- VolumetricMaxPooling:
|
||||
- kernelTime:
|
||||
min: 1
|
||||
- kernelWidth:
|
||||
min: 1
|
||||
- kernelHeight:
|
||||
min: 1
|
||||
- strideTime:
|
||||
min: 1
|
||||
default: 1
|
||||
- strideWidth:
|
||||
min: 1
|
||||
default: 1
|
||||
- strideHeight:
|
||||
min: 1
|
||||
default: 1
|
||||
|
||||
SimpleLayer:
|
||||
- Linear: # FIXME: These should contain the actual args
|
||||
- input:
|
||||
infer: dimensionality
|
||||
- output:
|
||||
min: 1
|
||||
- SparseLinear:
|
||||
- input:
|
||||
infer: dimensionality
|
||||
- output:
|
||||
min: 1
|
||||
|
||||
- Dropout:
|
||||
- probability:
|
||||
type: float
|
||||
- Abs:
|
||||
- Add:
|
||||
- isScalar:
|
||||
type: boolean
|
||||
- Mul:
|
||||
- CMul:
|
||||
- size: null
|
||||
- Max:
|
||||
- dimension:
|
||||
min: 0
|
||||
- Min:
|
||||
- dimension:
|
||||
min: 0
|
||||
- Mean:
|
||||
- dimension:
|
||||
min: 0
|
||||
- Sum:
|
||||
- dimension:
|
||||
min: 0
|
||||
|
||||
- Euclidean:
|
||||
- output:
|
||||
min: 0
|
||||
- WeightedEuclidean:
|
||||
- output:
|
||||
min: 0
|
||||
- Identity:
|
||||
- Copy: # Casts types
|
||||
- inputType:
|
||||
type: string
|
||||
- outputType:
|
||||
type: string
|
||||
- forceCopy:
|
||||
type: boolean
|
||||
- Narrow:
|
||||
- dimension:
|
||||
min: 0
|
||||
- offset:
|
||||
min: 0
|
||||
- length:
|
||||
min: 0
|
||||
- Replicate:
|
||||
- nFeature:
|
||||
min: 0
|
||||
- Reshape:
|
||||
- dimensions:
|
||||
type: list
|
||||
- View:
|
||||
- sizes: # list
|
||||
type: list
|
||||
min: 0
|
||||
- Select:
|
||||
- dimensions:
|
||||
type: list
|
||||
- Exp
|
||||
- Square
|
||||
- Sqrt
|
||||
- Power:
|
||||
- p: null
|
||||
- MM:
|
||||
- transA:
|
||||
type: boolean
|
||||
- transB:
|
||||
type: boolean
|
||||
|
||||
TransferLayer:
|
||||
- HardTanh
|
||||
- HardShrink:
|
||||
- lambda:
|
||||
type: float
|
||||
- SoftShrink:
|
||||
- lambda:
|
||||
type: float
|
||||
- SoftMax
|
||||
- SoftMin
|
||||
- SoftPlus
|
||||
- SoftSign # Typo in the docs on this one
|
||||
- LogSigmoid
|
||||
- LogSoftMax # Also in Criterion?
|
||||
- Sigmoid
|
||||
- Tanh
|
||||
- ReLU
|
||||
- PReLU # Missing from docs
|
||||
- RReLU # Missing from docs
|
||||
- LeakyReLU # Missing from docs
|
||||
- AddConstant:
|
||||
- scalar:
|
||||
type: float
|
||||
- MulConstant:
|
||||
- scalar:
|
||||
type: float
|
||||
min: 1
|
||||
- inplace:
|
||||
default: false
|
||||
|
||||
Criterion:
|
||||
- BCECriterion
|
||||
- WeightedMSECriterion
|
||||
- SmoothL1Criterion
|
||||
- MSECriterion
|
||||
- AbsCriterion
|
||||
- MultiCriterion
|
||||
- DistKLDivCriterion
|
||||
- HingeEmbeddingCriterion
|
||||
- CriterionTable
|
||||
- MultiMarginCriterion
|
||||
- MultiLabelMarginCriterion
|
||||
- L1HingeEmbeddingCriterion
|
||||
- CosineEmbeddingCriterion
|
||||
- MarginRankingCriterion
|
||||
- CrossEntropyCriterion
|
||||
- MarginCriterion
|
||||
- ClassNLLCriterion
|
||||
- ParallelCriterion
|
||||
|
||||
MiscLayers:
|
||||
- Jacobian
|
||||
- ConcatTable
|
||||
- CMulTable
|
||||
- CAddTable
|
||||
- TanhShrink
|
||||
- Padding:
|
||||
- dim:
|
||||
- pad:
|
||||
min: 0
|
||||
- nInputDim: # TODO: infer?
|
||||
min: 1
|
||||
- value:
|
||||
min: 0
|
||||
default: 0
|
||||
|
||||
# TODO: Add the following layers
|
||||
#VolumetricMaxUnpooling
|
||||
# Takes a poolingModule as an arg...
|
||||
|
||||
#MixtureTable
|
||||
#NarrowTable
|
||||
#SplitTable
|
||||
#DotProduct
|
||||
#DepthConcat
|
||||
#Parallel
|
||||
#Log
|
||||
#hessian
|
||||
#ELU
|
||||
#CSubTable
|
||||
#VolumetricAveragePooling
|
||||
#StochasticGradient
|
||||
#Bilinear
|
||||
#VolumetricFullConvolution
|
||||
#SparseJacobian
|
||||
#Contiguous
|
||||
#L1Cost
|
||||
#JoinTable
|
||||
#CosineDistance
|
||||
#Index
|
||||
#L1Penalty
|
||||
#Cosine
|
||||
#Clamp
|
||||
#SpatialConvolutionMM
|
||||
#LogSigmoid
|
||||
#ParallelTable
|
||||
#CDivTable
|
||||
#SpatialFullConvolutionMap
|
||||
#GradientReversal
|
||||
#SpatialMaxUnpooling
|
||||
#Transpose
|
||||
#Normalize
|
||||
#SpatialSoftMax
|
||||
#SelectTable
|
||||
#FlattenTable
|
||||
|
||||
# CONTAINERS and TableLayouts
|
||||
# Some of these are captured by the visual structure of the architecture and are not needed
|
||||
# as explicit layers in the metamodel
|
||||
#TableLayer:
|
||||
#- ConcatTable
|
||||
#Container:
|
||||
|
||||
@@ -2565,7 +2565,7 @@ function LuaContext(){
|
||||
case 'number':
|
||||
return c;
|
||||
case 'string':
|
||||
return parseInt(s) || dummy0;
|
||||
return parseInt(c) || dummy0;
|
||||
default:
|
||||
if (c == dummy0){
|
||||
return c;
|
||||
|
||||
@@ -2,14 +2,16 @@
|
||||
// This is an 'executor' containing the implementations of all local operations
|
||||
// These are all primitives in DeepForge
|
||||
define([
|
||||
'deepforge/Constants'
|
||||
], function(
|
||||
CONSTANTS
|
||||
) {
|
||||
'use strict';
|
||||
var LocalExecutor = function() {
|
||||
};
|
||||
|
||||
// Should these be in lua?
|
||||
LocalExecutor.prototype.ArtifactLoader = function(node) {
|
||||
LocalExecutor.prototype[CONSTANTS.OP.INPUT] = function(node) {
|
||||
// Get the hash from the output node
|
||||
var hash;
|
||||
return this.core.loadChildren(node)
|
||||
@@ -18,13 +20,13 @@ define([
|
||||
var output = cntrs
|
||||
.find(cntr => {
|
||||
var metaNode = this.core.getMetaType(cntr),
|
||||
metaName = this.core.getAttribute(metaNode, 'name');
|
||||
metaName = this.getAttribute(metaNode, 'name');
|
||||
return metaName === 'Outputs';
|
||||
});
|
||||
return this.core.loadChildren(output);
|
||||
})
|
||||
.then(dataNodes => {
|
||||
hash = this.core.getAttribute(dataNodes[0], 'data');
|
||||
hash = this.getAttribute(dataNodes[0], 'data');
|
||||
return this.getOutputs(node);
|
||||
})
|
||||
.then(outputTuples => {
|
||||
@@ -46,7 +48,7 @@ define([
|
||||
var hash,
|
||||
typeId = this.core.getPointerPath(node, 'type'),
|
||||
type,
|
||||
artifactName = this.core.getAttribute(node, 'artifactName');
|
||||
artifactName = this.getAttribute(node, 'artifactName');
|
||||
|
||||
return this.core.loadByPath(this.rootNode, typeId)
|
||||
.then(_type => {
|
||||
@@ -56,25 +58,20 @@ define([
|
||||
.then(saveDir => this.core.loadChildren(saveDir))
|
||||
.then(artifacts => {
|
||||
return artifacts.find(artifact =>
|
||||
this.core.getAttribute(artifact, 'name') === artifactName &&
|
||||
this.getAttribute(artifact, 'name') === artifactName &&
|
||||
this.isMetaTypeOf(artifact, type));
|
||||
})
|
||||
.then(matchingArtifact => {
|
||||
hash = matchingArtifact && this.core.getAttribute(matchingArtifact, 'data');
|
||||
hash = matchingArtifact && this.getAttribute(matchingArtifact, 'data');
|
||||
// If no hash, just continue (the subsequent ops will receive 'nil')
|
||||
if (!hash) {
|
||||
return this.onOperationComplete(node);
|
||||
} else {
|
||||
return this.getOutputs(node)
|
||||
.then(outputPairs => {
|
||||
var outputs = outputPairs.map(pair => pair[2]),
|
||||
paths;
|
||||
|
||||
paths = outputs.map(output => this.core.getPath(output));
|
||||
var outputs = outputPairs.map(pair => pair[2]);
|
||||
// Get the 'data' hash and store it in the output data ports
|
||||
this.logger.info(`Loading blob data (${hash}) to ${paths.map(p => `"${p}"`)}`);
|
||||
|
||||
outputs.forEach(output => this.core.setAttribute(output, 'data', hash));
|
||||
outputs.forEach(output => this.setAttribute(output, 'data', hash));
|
||||
|
||||
this.onOperationComplete(node);
|
||||
});
|
||||
@@ -97,7 +94,7 @@ define([
|
||||
|
||||
if (containers.length > 1) {
|
||||
saveDir = containers.find(c =>
|
||||
this.core.getAttribute(c, 'name').toLowerCase().indexOf('artifacts') > -1
|
||||
this.getAttribute(c, 'name').toLowerCase().indexOf('artifacts') > -1
|
||||
) || containers[0];
|
||||
}
|
||||
|
||||
@@ -105,22 +102,31 @@ define([
|
||||
});
|
||||
};
|
||||
|
||||
LocalExecutor.prototype.Save = function(node) {
|
||||
var nodeId = this.core.getPath(node),
|
||||
parentNode;
|
||||
LocalExecutor.prototype[CONSTANTS.OP.OUTPUT] = function(node) {
|
||||
var parentNode,
|
||||
currNameHashPairs;
|
||||
|
||||
// Get the input node
|
||||
this.logger.info('Calling save operation!');
|
||||
return this._getSaveDir()
|
||||
.then(_saveDir => {
|
||||
parentNode = _saveDir;
|
||||
return this.core.loadChildren(_saveDir);
|
||||
})
|
||||
.then(artifacts => {
|
||||
currNameHashPairs = artifacts
|
||||
.map(node => [
|
||||
this.getAttribute(node, 'name'),
|
||||
this.getAttribute(node, 'data')
|
||||
]);
|
||||
return this.getInputs(node);
|
||||
})
|
||||
.then(inputs => {
|
||||
var ids = inputs.map(i => this.core.getPath(i[2])),
|
||||
allDataNodes,
|
||||
dataNodes;
|
||||
|
||||
dataNodes = Object.keys(this.nodes)
|
||||
allDataNodes = Object.keys(this.nodes)
|
||||
.map(id => this.nodes[id])
|
||||
.filter(node => this.isMetaTypeOf(node, this.META.Transporter))
|
||||
.filter(node =>
|
||||
@@ -129,20 +135,36 @@ define([
|
||||
.map(node => this.core.getPointerPath(node, 'src'))
|
||||
.map(id => this.nodes[id]);
|
||||
|
||||
// Remove nodes that already exist
|
||||
dataNodes = allDataNodes.filter(dataNode => {
|
||||
var hash = this.getAttribute(dataNode, 'data'),
|
||||
name = this.core.getOwnAttribute(node, 'saveName') ||
|
||||
this.getAttribute(dataNode, 'name');
|
||||
|
||||
return !(currNameHashPairs
|
||||
.find(pair => pair[0] === name && pair[1] === hash));
|
||||
});
|
||||
|
||||
// get the input node
|
||||
if (dataNodes.length === 0) {
|
||||
this.logger.error(`Could not find data to save! ${nodeId}`);
|
||||
} else {
|
||||
if (dataNodes.length !== 0) {
|
||||
var newNodes = this.core.copyNodes(dataNodes, parentNode),
|
||||
newName = this.core.getOwnAttribute(node, 'saveName');
|
||||
newName = this.core.getOwnAttribute(node, 'saveName'),
|
||||
createdAt = Date.now();
|
||||
|
||||
if (newName) {
|
||||
newNodes.forEach(node =>
|
||||
this.core.setAttribute(node, 'name', newName)
|
||||
);
|
||||
newNodes.forEach(node => {
|
||||
this.setAttribute(node, 'name', newName);
|
||||
this.setAttribute(node, 'createdAt', createdAt);
|
||||
});
|
||||
}
|
||||
var hashes = dataNodes.map(n => this.getAttribute(n, 'data'));
|
||||
this.logger.info(`saving hashes: ${hashes.map(h => `"${h}"`)}`);
|
||||
} else if (allDataNodes.length === 0) {
|
||||
this.logger.warn('No data nodes found!');
|
||||
} else {
|
||||
this.logger.info('Using cached artifact(s)');
|
||||
}
|
||||
var hashes = dataNodes.map(n => this.core.getAttribute(n, 'data'));
|
||||
this.logger.info(`saving hashes: ${hashes.map(h => `"${h}"`)}`);
|
||||
|
||||
this.onOperationComplete(node);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/*globals define */
|
||||
// This is a mixin containing helpers for working with operation nodes
|
||||
define([],function() {
|
||||
|
||||
var OperationOps = function() {
|
||||
};
|
||||
|
||||
OperationOps.prototype.getOutputs = function (node) {
|
||||
return this.getOperationData(node, this.META.Outputs);
|
||||
};
|
||||
|
||||
OperationOps.prototype.getInputs = function (node) {
|
||||
return this.getOperationData(node, this.META.Inputs);
|
||||
};
|
||||
|
||||
OperationOps.prototype.getOperationData = function (node, metaType) {
|
||||
// Load the children and the output's children
|
||||
return this.core.loadChildren(node)
|
||||
.then(containers => {
|
||||
var outputs = containers.find(c => this.core.isTypeOf(c, metaType));
|
||||
return outputs ? this.core.loadChildren(outputs) : [];
|
||||
})
|
||||
.then(outputs => {
|
||||
var bases = outputs.map(node => this.core.getMetaType(node));
|
||||
// return [[arg1, Type1, node1], [arg2, Type2, node2]]
|
||||
return outputs.map((node, i) => [
|
||||
this.getAttribute(node, 'name'),
|
||||
this.getAttribute(bases[i], 'name'),
|
||||
node
|
||||
]);
|
||||
});
|
||||
};
|
||||
|
||||
return OperationOps;
|
||||
});
|
||||
@@ -1,47 +1,120 @@
|
||||
/*globals define, WebGMEGlobal*/
|
||||
/*globals define, requirejs*/
|
||||
define([
|
||||
'plugin/util',
|
||||
'q'
|
||||
], function(
|
||||
PluginUtils,
|
||||
Q
|
||||
) {
|
||||
|
||||
var CodeGen = {
|
||||
Operation: {
|
||||
pluginId: 'GenerateJob',
|
||||
namespace: 'pipeline'
|
||||
}
|
||||
};
|
||||
|
||||
var PtrCodeGen = function() {
|
||||
};
|
||||
|
||||
PtrCodeGen.prototype.getCodeGenPluginIdFor = function(node) {
|
||||
var base = this.core.getBase(node),
|
||||
name = this.core.getAttribute(node, 'name'),
|
||||
namespace = this.core.getNamespace(node),
|
||||
pluginId;
|
||||
|
||||
//this.logger.debug(`loaded pointer target of ${ptrId}: ${ptrNode}`);
|
||||
pluginId = (this.core.getOwnRegistry(node, 'validPlugins') || '').split(' ').shift();
|
||||
//this.logger.info(`generating code for ${this.core.getAttribute(ptrNode, 'name')} using ${pluginId}`);
|
||||
|
||||
if (this.core.isMetaNode(node) && CodeGen[name]) {
|
||||
pluginId = CodeGen[name].pluginId || CodeGen[name];
|
||||
namespace = CodeGen[name].namespace;
|
||||
}
|
||||
|
||||
if (pluginId) {
|
||||
return {
|
||||
namespace: namespace,
|
||||
pluginId: pluginId
|
||||
};
|
||||
} else if (base) {
|
||||
return this.getCodeGenPluginIdFor(base);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
PtrCodeGen.prototype.getPtrCodeHash = function(ptrId) {
|
||||
return this.core.loadByPath(this.rootNode, ptrId)
|
||||
.then(ptrNode => {
|
||||
// Look up the plugin to use
|
||||
var metanode = this.core.getMetaType(ptrNode),
|
||||
pluginId;
|
||||
var genInfo = this.getCodeGenPluginIdFor(ptrNode);
|
||||
|
||||
pluginId = this.core.getRegistry(ptrNode, 'validPlugins').split(' ').shift();
|
||||
this.logger.info(`generating code for ${this.core.getAttribute(ptrNode, 'name')} using ${pluginId}`);
|
||||
if (genInfo.pluginId) {
|
||||
var context = {
|
||||
namespace: genInfo.namespace,
|
||||
activeNode: this.core.getPath(ptrNode)
|
||||
};
|
||||
|
||||
var context = WebGMEGlobal.Client.getCurrentPluginContext(pluginId);
|
||||
|
||||
context.managerConfig.namespace = this.core.getNamespace(metanode);
|
||||
context.managerConfig.activeNode = this.core.getPath(ptrNode);
|
||||
|
||||
// Load and run the plugin
|
||||
return Q.nfcall(this.executePlugin.bind(this), pluginId, context);
|
||||
// Load and run the plugin
|
||||
return this.executePlugin(genInfo.pluginId, context);
|
||||
} else {
|
||||
var metanode = this.core.getMetaType(ptrNode),
|
||||
type = this.core.getAttribute(metanode, 'name');
|
||||
this.logger.warn(`Could not find plugin for ${type}. Will try to proceed anyway`);
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.then(hashes => hashes[0]); // Grab the first asset for now
|
||||
};
|
||||
|
||||
PtrCodeGen.prototype.executePlugin = function(pluginId, config, callback) {
|
||||
// Call the Interpreter manager in a Q.ninvoke friendly way
|
||||
// I need to create a custom context for the given plugin:
|
||||
// - Set the activeNode to the given referenced node
|
||||
// - If the activeNode is namespaced, set META to the given namespace
|
||||
//
|
||||
// FIXME: Check if it is running in the browser or on the server
|
||||
WebGMEGlobal.Client.runBrowserPlugin(pluginId, config, (err, result) => {
|
||||
if (!result.success) {
|
||||
return callback(result.getError());
|
||||
}
|
||||
this.logger.info('Finished calling ' + pluginId);
|
||||
callback(null, result.artifacts);
|
||||
PtrCodeGen.prototype.createPlugin = function(pluginId) {
|
||||
var deferred = Q.defer(),
|
||||
pluginPath = [
|
||||
'plugin',
|
||||
pluginId,
|
||||
pluginId,
|
||||
pluginId
|
||||
].join('/');
|
||||
|
||||
requirejs([pluginPath], Plugin => {
|
||||
var plugin = new Plugin();
|
||||
deferred.resolve(plugin);
|
||||
}, err => {
|
||||
this.logger.error(`Could not load ${pluginId}: ${err}`);
|
||||
deferred.reject(err);
|
||||
});
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
PtrCodeGen.prototype.configurePlugin = function(plugin, opts) {
|
||||
var logger = this.logger.fork(plugin.getName());
|
||||
|
||||
return PluginUtils.loadNodesAtCommitHash(
|
||||
this.project,
|
||||
this.core,
|
||||
this.currentHash,
|
||||
this.logger,
|
||||
opts
|
||||
).then(config => {
|
||||
plugin.initialize(logger, this.blobClient, this.gmeConfig);
|
||||
config.core = this.core;
|
||||
config.project = this.project;
|
||||
plugin.configure(config);
|
||||
return plugin;
|
||||
});
|
||||
};
|
||||
|
||||
PtrCodeGen.prototype.executePlugin = function(pluginId, config) {
|
||||
return this.createPlugin(pluginId)
|
||||
.then(plugin => this.configurePlugin(plugin, config))
|
||||
.then(plugin => {
|
||||
return Q.ninvoke(plugin, 'main');
|
||||
})
|
||||
.then(result => {
|
||||
this.logger.info('Finished calling ' + pluginId);
|
||||
return result.artifacts;
|
||||
});
|
||||
};
|
||||
|
||||
return PtrCodeGen;
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Audiowide';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Audiowide'), local('Audiowide-Regular'), url(https://fonts.gstatic.com/s/audiowide/v4/7pSgz2MbVvTCvvm7vukSHxJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
|
||||
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Audiowide';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Audiowide'), local('Audiowide-Regular'), url(https://fonts.gstatic.com/s/audiowide/v4/8XtYtNKEyyZh481XVWfVOltXRa8TVwTICgirnJhmVJw.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
|
||||
li.deepforge-logo {
|
||||
background-image: url(img/deepforge-logo.png);
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
width: 100px;
|
||||
background-size: 95px;
|
||||
}
|
||||
|
||||
.deepforge-logo .item-label {
|
||||
font-family: 'Audiowide', cursive;
|
||||
li.deepforge-logo span {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
i.gme-icon {
|
||||
background-image: url(img/deepforge-icon.png);
|
||||
background-size: 15.20px 18px;
|
||||
}
|
||||
|
||||
.create-node text {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.job-canceled {
|
||||
background-color: #ffe0b2;
|
||||
}
|
||||
|
||||
|
Depois Largura: | Altura: | Tamanho: 2.0 KiB |
|
Depois Largura: | Altura: | Tamanho: 3.3 KiB |
@@ -0,0 +1,93 @@
|
||||
/* globals define*/
|
||||
(function(root, factory){
|
||||
if(typeof define === 'function' && define.amd) {
|
||||
define([], function(){
|
||||
return (root.utils = factory());
|
||||
});
|
||||
} else if(typeof module === 'object' && module.exports) {
|
||||
module.exports = (root.utils = factory());
|
||||
}
|
||||
}(this, function() {
|
||||
var isBoolean = txt => {
|
||||
return typeof txt === 'boolean' || (txt === 'false' || txt === 'true');
|
||||
};
|
||||
|
||||
var getSetterSchema = function(name, setters, defaults) {
|
||||
var values,
|
||||
schema = setters[name];
|
||||
|
||||
if (defaults.hasOwnProperty(name)) {
|
||||
schema.default = defaults[name];
|
||||
}
|
||||
schema.type = 'string';
|
||||
if (schema.setterType === 'const') {
|
||||
values = Object.keys(schema.setterFn);
|
||||
schema.isEnum = true;
|
||||
schema.enumValues = values;
|
||||
if (values.every(isBoolean)) {
|
||||
if (!defaults.hasOwnProperty(name) && values.length === 1) {
|
||||
// there is only a method to toggle the flag to true/false,
|
||||
// then the default must be the other one
|
||||
schema.default = values[0] === 'true' ? false : true;
|
||||
}
|
||||
|
||||
if (isBoolean(schema.default)) {
|
||||
schema.type = 'boolean';
|
||||
}
|
||||
}
|
||||
}
|
||||
return schema;
|
||||
};
|
||||
|
||||
var abbrWord = function(word) { // camelcase
|
||||
word = word.substring(0, 1).toUpperCase() + word.substring(1);
|
||||
return word.split(/[a-z]+/g).join('').toLowerCase();
|
||||
};
|
||||
|
||||
var abbrPhrase = function(words) { // dashes, spaces, underscores, etc
|
||||
return words.map(word => word[0]).join('');
|
||||
};
|
||||
|
||||
var abbr = function(phrase) {
|
||||
var words = phrase.split(/[^a-zA-Z0-9]+/g);
|
||||
if (words.length === 1) {
|
||||
return abbrWord(phrase);
|
||||
} else {
|
||||
return abbrPhrase(words);
|
||||
}
|
||||
};
|
||||
|
||||
// Resolving stdout
|
||||
var resolveCarriageReturns = function(text) {
|
||||
// resolve \r
|
||||
var lines,
|
||||
chars,
|
||||
result,
|
||||
i = 0;
|
||||
|
||||
text = text.replace(/\u0000/g, '');
|
||||
lines = text.split('\n');
|
||||
for (var l = lines.length-1; l >= 0; l--) {
|
||||
i = 0;
|
||||
chars = lines[l].split('');
|
||||
result = [];
|
||||
|
||||
for (var c = 0; c < chars.length; c++) {
|
||||
if (chars[c] === '\r') {
|
||||
i = 0;
|
||||
}
|
||||
result[i] = chars[c];
|
||||
i++;
|
||||
}
|
||||
lines[l] = result.join('');
|
||||
}
|
||||
return lines;
|
||||
};
|
||||
|
||||
|
||||
return {
|
||||
getSetterSchema: getSetterSchema,
|
||||
resolveCarriageReturns: resolveCarriageReturns,
|
||||
abbr: abbr
|
||||
};
|
||||
}));
|
||||
@@ -1,8 +1,10 @@
|
||||
/*globals define, WebGMEGlobal*/
|
||||
define([
|
||||
'deepforge/globals',
|
||||
'widgets/EasyDAG/Buttons',
|
||||
'widgets/EasyDAG/Icons'
|
||||
], function(
|
||||
DeepForge,
|
||||
EasyDAGButtons,
|
||||
Icons
|
||||
) {
|
||||
@@ -57,9 +59,79 @@ define([
|
||||
return n && n.getBaseId();
|
||||
};
|
||||
|
||||
var CloneAndEdit = function(params) {
|
||||
GoToBase.call(this, params);
|
||||
};
|
||||
|
||||
CloneAndEdit.prototype = Object.create(GoToBase.prototype);
|
||||
CloneAndEdit.prototype.BTN_CLASS = 'clone-and-edit';
|
||||
|
||||
CloneAndEdit.prototype._render = function() {
|
||||
var lineRadius = GoToBase.SIZE - GoToBase.BORDER,
|
||||
btnColor = '#a5d6a7';
|
||||
|
||||
if (this.disabled) {
|
||||
btnColor = '#e0e0e0';
|
||||
}
|
||||
|
||||
this.$el
|
||||
.append('circle')
|
||||
.attr('r', GoToBase.SIZE)
|
||||
.attr('fill', btnColor);
|
||||
|
||||
// Show the 'code' icon
|
||||
Icons.addIcon('code', this.$el, {
|
||||
radius: lineRadius
|
||||
});
|
||||
};
|
||||
|
||||
CloneAndEdit.prototype._onClick = function(item) {
|
||||
var node = client.getNode(item.id),
|
||||
baseId = node && node.getBaseId(),
|
||||
base = baseId && client.getNode(baseId),
|
||||
typeId = base && base.getBaseId(),
|
||||
type = typeId && client.getNode(typeId),
|
||||
ctrName,
|
||||
typeName,
|
||||
name,
|
||||
newId;
|
||||
|
||||
// Clone the given node's base and change to it
|
||||
if (type) {
|
||||
typeName = type.getAttribute('name');
|
||||
ctrName = `My${typeName}s`;
|
||||
if (DeepForge.places[ctrName]) {
|
||||
DeepForge.places[ctrName]().then(ctrId => {
|
||||
type = base.getAttribute('name');
|
||||
client.startTransaction(`Creating new ${typeName} from ${item.name}`);
|
||||
newId = client.copyNode(baseId, ctrId);
|
||||
name = node.getAttribute('name');
|
||||
client.setAttribute(newId, 'name', 'Copy of ' + name);
|
||||
DeepForge.register[typeName](newId);
|
||||
|
||||
client.completeTransaction();
|
||||
WebGMEGlobal.State.registerActiveObject(newId);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this._logger.warn('Could not find the base node!');
|
||||
}
|
||||
};
|
||||
|
||||
var Insert = function(params) {
|
||||
EasyDAGButtons.ButtonBase.call(this, params);
|
||||
};
|
||||
|
||||
Insert.prototype = Object.create(EasyDAGButtons.Add.prototype);
|
||||
Insert.prototype._onClick = function(item) {
|
||||
this.onInsertButtonClicked(item);
|
||||
};
|
||||
|
||||
return {
|
||||
DeleteOne: EasyDAGButtons.DeleteOne,
|
||||
GoToBase: GoToBase
|
||||
GoToBase: GoToBase,
|
||||
CloneAndEdit: CloneAndEdit,
|
||||
Insert: Insert
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,285 @@
|
||||
/* globals define, WebGMEGlobal */
|
||||
// Mixin for executing jobs and pipelines
|
||||
define([
|
||||
'q',
|
||||
'executor/ExecutorClient',
|
||||
'deepforge/api/ExecPulseClient',
|
||||
'deepforge/api/JobOriginClient',
|
||||
'deepforge/Constants',
|
||||
'panel/FloatingActionButton/styles/Materialize'
|
||||
], function(
|
||||
Q,
|
||||
ExecutorClient,
|
||||
ExecPulseClient,
|
||||
JobOriginClient,
|
||||
CONSTANTS,
|
||||
Materialize
|
||||
) {
|
||||
|
||||
var Execute = function(client, logger) {
|
||||
this.client = this.client || client;
|
||||
this.logger = this.logger || logger;
|
||||
this.pulseClient = new ExecPulseClient({
|
||||
logger: this.logger
|
||||
});
|
||||
this._executor = new ExecutorClient({
|
||||
logger: this.logger.fork('ExecutorClient'),
|
||||
serverPort: WebGMEGlobal.gmeConfig.server.port,
|
||||
httpsecure: window.location.protocol === 'https:'
|
||||
});
|
||||
this.originManager = new JobOriginClient({logger: this.logger});
|
||||
};
|
||||
|
||||
Execute.prototype.executeJob = function(node) {
|
||||
return this.runExecutionPlugin('ExecuteJob', {node: node});
|
||||
};
|
||||
|
||||
Execute.prototype.executePipeline = function(node) {
|
||||
return this.runExecutionPlugin('ExecutePipeline', {node: node});
|
||||
};
|
||||
|
||||
Execute.prototype.runExecutionPlugin = function(pluginId, opts) {
|
||||
var context = this.client.getCurrentPluginContext(pluginId),
|
||||
node = opts.node || this.client.getNode(this._currentNodeId),
|
||||
name = node.getAttribute('name'),
|
||||
method;
|
||||
|
||||
// Set the activeNode
|
||||
context.managerConfig.namespace = 'pipeline';
|
||||
context.managerConfig.activeNode = node.getId();
|
||||
method = opts.useSecondary ? 'runBrowserPlugin' : 'runServerPlugin';
|
||||
|
||||
if (method === 'runServerPlugin' &&
|
||||
this.client.getBranchStatus() !== this.client.CONSTANTS.BRANCH_STATUS.SYNC) {
|
||||
|
||||
Materialize.toast('Cannot execute operations when client is out-of-sync', 2000);
|
||||
return;
|
||||
}
|
||||
|
||||
this.client[method](pluginId, context, (err, result) => {
|
||||
var msg = err ? `${name} failed!` : `${name} executed successfully!`,
|
||||
duration = err ? 4000 : 2000;
|
||||
|
||||
// Check if it was canceled - if so, show that type of message
|
||||
if (result && result.messages.length) {
|
||||
msg = result.messages[0].message;
|
||||
duration = 4000;
|
||||
}
|
||||
|
||||
Materialize.toast(msg, duration);
|
||||
});
|
||||
};
|
||||
|
||||
Execute.prototype.isRunning = function(node) {
|
||||
var baseId,
|
||||
base,
|
||||
type;
|
||||
|
||||
node = node || this.client.getNode(this._currentNodeId);
|
||||
baseId = node.getBaseId();
|
||||
base = this.client.getNode(baseId);
|
||||
type = base.getAttribute('name');
|
||||
|
||||
if (type === 'Execution') {
|
||||
return node.getAttribute('status') === 'running';
|
||||
} else if (type === 'Job') {
|
||||
return this.isRunningJob(node);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
Execute.prototype.isRunningJob = function(job) {
|
||||
var status = job.getAttribute('status');
|
||||
|
||||
return (status === 'running' || status === 'pending') &&
|
||||
job.getAttribute('secret') && job.getAttribute('jobId');
|
||||
};
|
||||
|
||||
Execute.prototype.silentStopJob = function(job) {
|
||||
var jobHash,
|
||||
secret;
|
||||
|
||||
job = job || this.client.getNode(this._currentNodeId);
|
||||
jobHash = job.getAttribute('jobId');
|
||||
secret = job.getAttribute('secret');
|
||||
if (!jobHash || !secret) {
|
||||
this.logger.error('Cannot stop job. Missing jobHash or secret');
|
||||
return;
|
||||
}
|
||||
|
||||
return this._executor.cancelJob(jobHash, secret)
|
||||
.then(() => this.logger.info(`${jobHash} has been cancelled!`))
|
||||
.fail(err => this.logger.error(`Job cancel failed: ${err}`));
|
||||
};
|
||||
|
||||
Execute.prototype._setJobStopped = function(jobId, silent) {
|
||||
if (!silent) {
|
||||
var name = this.client.getNode(jobId).getAttribute('name');
|
||||
this.client.startTransaction(`Stopping "${name}" job`);
|
||||
}
|
||||
|
||||
this.client.delAttribute(jobId, 'jobId');
|
||||
this.client.delAttribute(jobId, 'secret');
|
||||
this.client.setAttribute(jobId, 'status', 'canceled');
|
||||
|
||||
if (!silent) {
|
||||
this.client.completeTransaction();
|
||||
}
|
||||
};
|
||||
|
||||
Execute.prototype.stopJob = function(job, silent) {
|
||||
var jobId;
|
||||
|
||||
job = job || this.client.getNode(this._currentNodeId);
|
||||
jobId = job.getId();
|
||||
|
||||
this.silentStopJob(job);
|
||||
this._setJobStopped(jobId, silent);
|
||||
};
|
||||
|
||||
|
||||
Execute.prototype.loadChildren = function(id) {
|
||||
var deferred = Q.defer(),
|
||||
execNode = this.client.getNode(id || this._currentNodeId),
|
||||
jobIds = execNode.getChildrenIds(),
|
||||
jobsLoaded = !jobIds.length || this.client.getNode(jobIds[0]);
|
||||
|
||||
// May need to load the jobs...
|
||||
if (!jobsLoaded) {
|
||||
// Create a territory and load the nodes
|
||||
var territory = {},
|
||||
ui;
|
||||
|
||||
territory[id] = {children: 1};
|
||||
ui = this.client.addUI(this, () => {
|
||||
this.client.removeUI(ui);
|
||||
deferred.resolve();
|
||||
});
|
||||
this.client.updateTerritory(ui, territory);
|
||||
} else {
|
||||
deferred.resolve();
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
Execute.prototype.stopExecution = function(id, inTransaction) {
|
||||
var execNode = this.client.getNode(id || this._currentNodeId);
|
||||
|
||||
return this.loadChildren(id)
|
||||
.then(() => this._stopExecution(execNode, inTransaction));
|
||||
};
|
||||
|
||||
Execute.prototype.silentStopExecution = function(id) {
|
||||
var execNode = this.client.getNode(id || this._currentNodeId);
|
||||
|
||||
// Stop the execution w/o setting any attributes
|
||||
return this.loadChildren(id)
|
||||
.then(() => this._silentStopExecution(execNode));
|
||||
};
|
||||
|
||||
Execute.prototype._stopExecution = function(execNode, inTransaction) {
|
||||
var msg = `Canceling ${execNode.getAttribute('name')} execution`,
|
||||
jobIds;
|
||||
|
||||
if (!inTransaction) {
|
||||
this.client.startTransaction(msg);
|
||||
}
|
||||
|
||||
jobIds = this._silentStopExecution(execNode);
|
||||
|
||||
this.client.setAttribute(execNode.getId(), 'status', 'canceled');
|
||||
jobIds.forEach(jobId => this._setJobStopped(jobId, true));
|
||||
|
||||
if (!inTransaction) {
|
||||
this.client.completeTransaction();
|
||||
}
|
||||
};
|
||||
|
||||
Execute.prototype._silentStopExecution = function(execNode) {
|
||||
var runningJobIds = execNode.getChildrenIds()
|
||||
.map(id => this.client.getNode(id))
|
||||
.filter(job => this.isRunning(job)); // get running jobs
|
||||
|
||||
runningJobIds.forEach(job => this.silentStopJob(job)); // stop them
|
||||
|
||||
return runningJobIds;
|
||||
};
|
||||
|
||||
// Resuming Executions
|
||||
Execute.prototype.checkJobExecution= function (job) {
|
||||
var pipelineId = job.getParentId(),
|
||||
pipeline = this.client.getNode(pipelineId);
|
||||
|
||||
// First check the parent execution. If it doesn't exist, then check the job
|
||||
return this.checkPipelineExecution(pipeline)
|
||||
.then(tryToStartJob => {
|
||||
if (tryToStartJob) {
|
||||
return this._checkJobExecution(job);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
Execute.prototype._checkJobExecution = function (job) {
|
||||
var jobId = job.getAttribute('jobId'),
|
||||
status = job.getAttribute('status');
|
||||
|
||||
if (status === 'running' && jobId) {
|
||||
return this.pulseClient.check(jobId)
|
||||
.then(status => {
|
||||
if (status !== CONSTANTS.PULSE.DOESNT_EXIST) {
|
||||
return this._onOriginBranch(jobId).then(onBranch => {
|
||||
if (onBranch) {
|
||||
this.runExecutionPlugin('ExecuteJob', {
|
||||
node: job
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.logger.warn(`Could not restart job: ${job.getId()}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
return Q();
|
||||
};
|
||||
|
||||
Execute.prototype._onOriginBranch = function (hash) {
|
||||
return this.originManager.getOrigin(hash)
|
||||
.then(origin => {
|
||||
var currentBranch = this.client.getActiveBranchName();
|
||||
if (origin && origin.branch) {
|
||||
return origin.branch === currentBranch;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
};
|
||||
|
||||
Execute.prototype.checkPipelineExecution = function (pipeline) {
|
||||
var runId = pipeline.getAttribute('runId'),
|
||||
status = pipeline.getAttribute('status'),
|
||||
tryToStartJob = true;
|
||||
|
||||
if (status === 'running' && runId) {
|
||||
return this.pulseClient.check(runId)
|
||||
.then(status => {
|
||||
if (status === CONSTANTS.PULSE.DEAD) {
|
||||
// Check the origin branch
|
||||
return this._onOriginBranch(runId).then(onBranch => {
|
||||
if (onBranch) {
|
||||
this.runExecutionPlugin('ExecutePipeline', {
|
||||
node: pipeline
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
// only try to start if the pulse info doesn't exist
|
||||
tryToStartJob = status === CONSTANTS.PULSE.DOESNT_EXIST;
|
||||
return tryToStartJob;
|
||||
});
|
||||
} else {
|
||||
return Q().then(() => tryToStartJob);
|
||||
}
|
||||
};
|
||||
|
||||
return Execute;
|
||||
});
|
||||
@@ -4,9 +4,11 @@
|
||||
// adding a "plus" button for creating new objects in line
|
||||
|
||||
define([
|
||||
'deepforge/Constants',
|
||||
'q',
|
||||
'css!./NodePrompter.css'
|
||||
], function(
|
||||
Constants,
|
||||
Q
|
||||
) {
|
||||
|
||||
@@ -271,12 +273,14 @@ define([
|
||||
};
|
||||
|
||||
var Container = function(svg, node) { // used for positioning
|
||||
var colorAttr = node.attributes[Constants.DISPLAY_COLOR];
|
||||
this.$el = svg.append('g');
|
||||
this.x = 0;
|
||||
this.y = 0;
|
||||
this.node = node;
|
||||
this.decorator = new node.Decorator({
|
||||
node: node,
|
||||
color: colorAttr && colorAttr.value,
|
||||
parentEl: this.$el
|
||||
});
|
||||
};
|
||||
|
||||
@@ -3,11 +3,13 @@
|
||||
define([
|
||||
'panels/EasyDAG/EasyDAGControl',
|
||||
'deepforge/viz/OperationControl',
|
||||
'deepforge/Constants',
|
||||
'widgets/EasyDAG/AddNodeDialog',
|
||||
'underscore'
|
||||
], function(
|
||||
EasyDAGControl,
|
||||
OperationControl,
|
||||
CONSTANTS,
|
||||
AddNodeDialog,
|
||||
_
|
||||
) {
|
||||
@@ -63,7 +65,7 @@ define([
|
||||
|
||||
PipelineControl.prototype.createNode = function(baseId) {
|
||||
var parentId = this._currentNodeId,
|
||||
newNodeId = this._client.createChild({parentId, baseId});
|
||||
newNodeId = this._client.createNode({parentId, baseId});
|
||||
|
||||
return newNodeId;
|
||||
};
|
||||
@@ -72,6 +74,8 @@ define([
|
||||
var desc = EasyDAGControl.prototype._getObjectDescriptor.call(this, id),
|
||||
node = this._client.getNode(id);
|
||||
|
||||
desc.inputs = [];
|
||||
desc.outputs = [];
|
||||
if (this.hasMetaName(id, 'Operation')) {
|
||||
// Only decorate operations in the currently active node
|
||||
if (this._currentNodeId !== desc.parentId) {
|
||||
@@ -95,9 +99,14 @@ define([
|
||||
|
||||
// Remove the 'code' attribute
|
||||
if (desc.attributes.code) {
|
||||
delete desc.attributes[CONSTANTS.LINE_OFFSET];
|
||||
delete desc.attributes.code;
|
||||
}
|
||||
|
||||
// Handle the display color
|
||||
desc.displayColor = desc.attributes[CONSTANTS.DISPLAY_COLOR].value;
|
||||
delete desc.attributes[CONSTANTS.DISPLAY_COLOR];
|
||||
|
||||
} else if (desc.isConnection) {
|
||||
// Set src, dst to siblings and add srcPort, dstPort
|
||||
desc.srcPort = desc.src;
|
||||
|
||||
@@ -21,7 +21,7 @@ define([
|
||||
|
||||
RenameablePanel.OPTIONS = PanelBaseWithHeader.OPTIONS;
|
||||
RenameablePanel.prototype.initializeRenameable = function () {
|
||||
this.$panelHeaderTitle.on('dblclick', this.editTitle.bind(this));
|
||||
this.$panelHeaderTitle.on('click', this.editTitle.bind(this));
|
||||
};
|
||||
|
||||
RenameablePanel.prototype.currentNodeId = function () {
|
||||
@@ -49,7 +49,7 @@ define([
|
||||
|
||||
if (!/^\s*$/.test(newValue)) {
|
||||
this._client.startTransaction(msg);
|
||||
this._client.setAttributes(nodeId, 'name', newValue);
|
||||
this._client.setAttribute(nodeId, 'name', newValue);
|
||||
this._client.completeTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
/* globals define*/
|
||||
define({
|
||||
getDisplayTime: timestamp => {
|
||||
var today = new Date().toLocaleDateString(),
|
||||
date = new Date(timestamp).toLocaleDateString();
|
||||
|
||||
if (date === today) {
|
||||
date = `Today (${new Date(timestamp).toLocaleTimeString()})`;
|
||||
}
|
||||
return date;
|
||||
},
|
||||
ClassForJobStatus: {
|
||||
success: 'success',
|
||||
canceled: 'job-canceled',
|
||||
failed: 'danger',
|
||||
pending: '',
|
||||
running: 'warning'
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
/* globals define */
|
||||
define([
|
||||
'panels/EasyDAG/EasyDAGControl'
|
||||
], function(
|
||||
EasyDAGControl
|
||||
) {
|
||||
var ThumbnailControl = function() {
|
||||
EasyDAGControl.apply(this, arguments);
|
||||
};
|
||||
|
||||
ThumbnailControl.prototype = Object.create(EasyDAGControl.prototype);
|
||||
|
||||
ThumbnailControl.prototype._initWidgetEventHandlers = function () {
|
||||
EasyDAGControl.prototype._initWidgetEventHandlers.call(this);
|
||||
this._widget.updateThumbnail = this.updateThumbnail.bind(this);
|
||||
};
|
||||
|
||||
ThumbnailControl.prototype.updateThumbnail = function (svg) {
|
||||
var node = this._client.getNode(this._currentNodeId),
|
||||
name,
|
||||
attrs,
|
||||
currentThumbnail,
|
||||
attrName = 'thumbnail',
|
||||
msg;
|
||||
|
||||
if (node) { // may have been deleted
|
||||
name = node.getAttribute('name');
|
||||
attrs = node.getValidAttributeNames();
|
||||
currentThumbnail = node.getAttribute(attrName);
|
||||
msg = `Updating pipeline thumbnail for "${name}"`;
|
||||
|
||||
if (attrs.indexOf(attrName) > -1 && currentThumbnail !== svg) {
|
||||
this._client.startTransaction(msg);
|
||||
this._client.setAttribute(this._currentNodeId, attrName, svg);
|
||||
this._client.completeTransaction();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return ThumbnailControl;
|
||||
});
|
||||
@@ -0,0 +1,84 @@
|
||||
/* globals define, $, _ */
|
||||
define([
|
||||
'widgets/EasyDAG/EasyDAGWidget'
|
||||
], function(
|
||||
EasyDAGWidget
|
||||
) {
|
||||
|
||||
var ThumbnailWidget = function() {
|
||||
EasyDAGWidget.apply(this, arguments);
|
||||
};
|
||||
|
||||
ThumbnailWidget.prototype = Object.create(EasyDAGWidget.prototype);
|
||||
|
||||
ThumbnailWidget.prototype.addNode = function() {
|
||||
var result = EasyDAGWidget.prototype.addNode.apply(this, arguments);
|
||||
|
||||
this.refreshThumbnail();
|
||||
return result;
|
||||
};
|
||||
|
||||
ThumbnailWidget.prototype.removeNode = function() {
|
||||
var result = EasyDAGWidget.prototype.removeNode.apply(this, arguments);
|
||||
|
||||
this.refreshThumbnail();
|
||||
return result;
|
||||
};
|
||||
|
||||
ThumbnailWidget.prototype._removeConnection = function() {
|
||||
var result = EasyDAGWidget.prototype._removeConnection.apply(this, arguments);
|
||||
|
||||
this.refreshThumbnail();
|
||||
return result;
|
||||
};
|
||||
|
||||
ThumbnailWidget.prototype.addConnection = function() {
|
||||
var result = EasyDAGWidget.prototype.addConnection.apply(this, arguments);
|
||||
|
||||
this.refreshThumbnail();
|
||||
return result;
|
||||
};
|
||||
|
||||
////////////////////////// Thumbnail updates //////////////////////////
|
||||
ThumbnailWidget.prototype.getSvgDistanceDim = function(dim) {
|
||||
var maxValue = this._getMaxAlongAxis(dim),
|
||||
nodes,
|
||||
minValue;
|
||||
|
||||
nodes = this.graph.nodes().map(id => this.graph.node(id));
|
||||
minValue = nodes.length ? Math.min.apply(null, nodes.map(node => node[dim] || 0)) : 0;
|
||||
return maxValue-minValue;
|
||||
};
|
||||
|
||||
ThumbnailWidget.prototype.getSvgWidth = function() {
|
||||
return this.getSvgDistanceDim('x');
|
||||
};
|
||||
|
||||
ThumbnailWidget.prototype.getSvgHeight = function() {
|
||||
return this.height - 25;
|
||||
};
|
||||
|
||||
ThumbnailWidget.prototype.getViewBox = function() {
|
||||
var maxX = this.getSvgWidth('x'),
|
||||
maxY = this.getSvgHeight('y');
|
||||
|
||||
return `0 0 ${maxX} ${maxY}`;
|
||||
};
|
||||
|
||||
ThumbnailWidget.prototype.refreshThumbnail = _.debounce(function() {
|
||||
// Get the svg...
|
||||
var svg = document.createElement('svg'),
|
||||
group = this.$svg.node(),
|
||||
child;
|
||||
|
||||
svg.setAttribute('viewBox', this.getViewBox());
|
||||
for (var i = 0; i < group.children.length; i++) {
|
||||
child = $(group.children[i]);
|
||||
svg.appendChild(child.clone()[0]);
|
||||
}
|
||||
|
||||
this.updateThumbnail(svg.outerHTML);
|
||||
}, 1000);
|
||||
|
||||
return ThumbnailWidget;
|
||||
});
|
||||
@@ -1,12 +1,8 @@
|
||||
/*globals define, _*/
|
||||
/*jshint browser: true, camelcase: false*/
|
||||
|
||||
/**
|
||||
* @author brollb / https://github.com/brollb
|
||||
*/
|
||||
|
||||
define([
|
||||
'js/Constants',
|
||||
'deepforge/Constants',
|
||||
'decorators/DcOpDecorator/EasyDAG/DcOpDecorator.EasyDAGWidget',
|
||||
'css!./ArtifactOpDecorator.EasyDAGWidget.css'
|
||||
], function (
|
||||
@@ -19,16 +15,17 @@ define([
|
||||
var ArtifactOpDecorator,
|
||||
DECORATOR_ID = 'ArtifactOpDecorator',
|
||||
CAST_OPTS = {
|
||||
ArtifactLoader: {
|
||||
ptr: 'artifact',
|
||||
metaTgt: false
|
||||
},
|
||||
ArtifactFinder: {
|
||||
ptr: 'type',
|
||||
metaTgt: true
|
||||
}
|
||||
};
|
||||
|
||||
CAST_OPTS[CONSTANTS.OP.INPUT] = {
|
||||
ptr: 'artifact',
|
||||
metaTgt: false
|
||||
};
|
||||
|
||||
// ArtifactOp nodes need to be able to...
|
||||
// - dynamically change their outputs (downcast)
|
||||
ArtifactOpDecorator = function (options) {
|
||||
@@ -43,10 +40,40 @@ define([
|
||||
ArtifactOpDecorator.prototype.DECORATOR_ID = DECORATOR_ID;
|
||||
|
||||
ArtifactOpDecorator.prototype.getTargetFilterFnFor = function() {
|
||||
var currentNode,
|
||||
pId,
|
||||
peerIds,
|
||||
currentDataId,
|
||||
targetTypeIds = [];
|
||||
|
||||
// Get all connections from this node's data
|
||||
if (this._node.outputs[0]) {
|
||||
currentNode = this.client.getNode(this._node.id);
|
||||
pId = currentNode.getParentId();
|
||||
peerIds = this.client.getNode(pId).getChildrenIds();
|
||||
currentDataId = this._node.outputs[0].id;
|
||||
|
||||
targetTypeIds = peerIds.map(id => this.client.getNode(id))
|
||||
.filter(node => {
|
||||
var ptr = node.getPointer('src');
|
||||
return ptr.to === currentDataId;
|
||||
})
|
||||
|
||||
// Get the target data types
|
||||
.map(node => this.client.getNode(node.getPointer('dst').to).getMetaTypeId());
|
||||
}
|
||||
|
||||
return id => {
|
||||
var node = this.client.getNode(id),
|
||||
isMetaTgt = node.getId() === node.getMetaTypeId();
|
||||
return isMetaTgt === this.castOpts.metaTgt;
|
||||
isMetaTgt = node.getId() === node.getMetaTypeId(),
|
||||
isValidType = true;
|
||||
|
||||
// make sure it is a type of the target types
|
||||
isValidType = targetTypeIds.reduce((passing, typeId) => {
|
||||
return passing && node.isTypeOf(typeId);
|
||||
}, true);
|
||||
|
||||
return isValidType && isMetaTgt === this.castOpts.metaTgt;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -60,13 +87,13 @@ define([
|
||||
this.client.startTransaction(`Removing output of ${this.name}`);
|
||||
this.client.delPointer(this._node.id, name);
|
||||
if (outputId) {
|
||||
this.client.delAttributes(outputId, 'data');
|
||||
this.client.delAttribute(outputId, 'data');
|
||||
}
|
||||
this.client.completeTransaction();
|
||||
} else if (name === this.castOpts.ptr) { // set the casted value
|
||||
this.client.startTransaction(`Setting output of ${this.name} to ${to}`);
|
||||
this.castOutputType(to);
|
||||
this.client.makePointer(this._node.id, name, to);
|
||||
this.client.setPointer(this._node.id, name, to);
|
||||
this.client.completeTransaction();
|
||||
} else {
|
||||
DecoratorBase.prototype.savePointer.call(this, name, to);
|
||||
@@ -74,7 +101,7 @@ define([
|
||||
};
|
||||
|
||||
ArtifactOpDecorator.prototype.getDisplayName = function() {
|
||||
var ptrName = this._node.baseName === 'ArtifactLoader' ? 'artifact' : 'type',
|
||||
var ptrName = this._node.baseName === CONSTANTS.OP.INPUT ? 'artifact' : 'type',
|
||||
id = this._node.pointers[ptrName],
|
||||
name = this.nameFor[id] || this._node.name;
|
||||
return name;
|
||||
@@ -91,7 +118,7 @@ define([
|
||||
ArtifactOpDecorator.prototype.updateTargetName = function(id, name) {
|
||||
DecoratorBase.prototype.updateTargetName.apply(this, arguments);
|
||||
// Update name
|
||||
var ptrName = this._node.baseName === 'ArtifactLoader' ? 'artifact' : 'type';
|
||||
var ptrName = this._node.baseName === CONSTANTS.OP.INPUT ? 'artifact' : 'type';
|
||||
if (this._node.pointers[ptrName] === id) {
|
||||
this._name = name;
|
||||
this.onResize();
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
/*globals define, _*/
|
||||
/*jshint browser: true, camelcase: false*/
|
||||
|
||||
define([
|
||||
'js/Decorators/DecoratorBase',
|
||||
'./EasyDAG/ContainerLayerDecorator.EasyDAGWidget'
|
||||
], function (
|
||||
DecoratorBase,
|
||||
ContainerLayerDecoratorEasyDAGWidget
|
||||
) {
|
||||
|
||||
'use strict';
|
||||
|
||||
var ContainerLayerDecorator,
|
||||
__parent__ = DecoratorBase,
|
||||
__parent_proto__ = DecoratorBase.prototype,
|
||||
DECORATOR_ID = 'ContainerLayerDecorator';
|
||||
|
||||
ContainerLayerDecorator = function (params) {
|
||||
var opts = _.extend({loggerName: this.DECORATORID}, params);
|
||||
|
||||
__parent__.apply(this, [opts]);
|
||||
|
||||
this.logger.debug('ContainerLayerDecorator ctor');
|
||||
};
|
||||
|
||||
_.extend(ContainerLayerDecorator.prototype, __parent_proto__);
|
||||
ContainerLayerDecorator.prototype.DECORATORID = DECORATOR_ID;
|
||||
|
||||
/*********************** OVERRIDE DecoratorBase MEMBERS **************************/
|
||||
|
||||
ContainerLayerDecorator.prototype.initializeSupportedWidgetMap = function () {
|
||||
this.supportedWidgetMap = {
|
||||
EasyDAG: ContainerLayerDecoratorEasyDAGWidget
|
||||
};
|
||||
};
|
||||
|
||||
return ContainerLayerDecorator;
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
.condense .nested-layers {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.nested-layers .hover-box {
|
||||
stroke: black;
|
||||
stroke-dasharray: 4, 4;
|
||||
stroke-opacity: 0;
|
||||
}
|
||||
|
||||
.nested-layers .unhovered .button {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.nested-layers .hovered .button {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.nested-layers .unhovered .hover-box {
|
||||
stroke-opacity: 0;
|
||||
}
|
||||
|
||||
.nested-layers .hovered .hover-box {
|
||||
stroke-opacity: 0;
|
||||
}
|
||||
@@ -0,0 +1,428 @@
|
||||
/*globals define, _, */
|
||||
/*jshint browser: true, camelcase: false*/
|
||||
|
||||
define([
|
||||
'decorators/LayerDecorator/EasyDAG/LayerDecorator.EasyDAGWidget',
|
||||
'js/Constants',
|
||||
'deepforge/Constants',
|
||||
'./NestedLayer',
|
||||
'widgets/EasyDAG/Buttons',
|
||||
'css!./ContainerLayerDecorator.EasyDAGWidget.css'
|
||||
], function (
|
||||
LayerDecorator,
|
||||
GME_CONSTANTS,
|
||||
CONSTANTS,
|
||||
NestedLayer,
|
||||
Buttons
|
||||
) {
|
||||
|
||||
'use strict';
|
||||
|
||||
var ContainerLayerDecorator,
|
||||
ZOOM = 0.8,
|
||||
DECORATOR_ID = 'ContainerLayerDecorator';
|
||||
|
||||
// Container layer nodes need to be able to nest the containedLayers
|
||||
// in order inside of themselves when expanded
|
||||
ContainerLayerDecorator = function (options) {
|
||||
this.nestedLayers = {};
|
||||
LayerDecorator.call(this, options);
|
||||
this.$nested = this.$el.append('g')
|
||||
.attr('class', 'nested-layers');
|
||||
|
||||
// If clicked, deselect the given nested layer
|
||||
this.$el.on('click', () => {
|
||||
if (this.expanded) {
|
||||
Object.keys(this.nestedLayers).forEach(id => {
|
||||
this.nestedLayers[id].widget.onBackgroundClick();
|
||||
});
|
||||
}
|
||||
});
|
||||
this.onNestedRefresh = _.debounce(this.updateExpand.bind(this), 50);
|
||||
|
||||
// Add event handlers
|
||||
NestedLayer.prototype.addLayerBefore = function(layerId) {
|
||||
var decorator = this._parent,
|
||||
index = decorator._node.containedLayers.indexOf(this.id);
|
||||
return decorator.addLayerAt(layerId, index - 1);
|
||||
};
|
||||
|
||||
NestedLayer.prototype.addLayerAfter = function(layerId) {
|
||||
var decorator = this._parent,
|
||||
index = decorator._node.containedLayers.indexOf(this.id);
|
||||
return decorator.addLayerAt(layerId, index + 1);
|
||||
};
|
||||
|
||||
NestedLayer.prototype.isLast = function() {
|
||||
var index = this._parent._node.containedLayers.length - 1;
|
||||
return this._parent._node.containedLayers[index] === this.id;
|
||||
};
|
||||
|
||||
NestedLayer.prototype.isFirst = function() {
|
||||
return this._parent._node.containedLayers[0] === this.id;
|
||||
};
|
||||
|
||||
NestedLayer.prototype.moveLayerForward = function() {
|
||||
return this.moveLayer(true);
|
||||
};
|
||||
|
||||
NestedLayer.prototype.moveLayerBackward = function() {
|
||||
return this.moveLayer();
|
||||
};
|
||||
|
||||
NestedLayer.prototype.moveLayer = function(forward) {
|
||||
var decorator = this._parent,
|
||||
index = decorator._node.containedLayers.indexOf(this.id),
|
||||
client = decorator.client,
|
||||
msg;
|
||||
|
||||
decorator._node.containedLayers.splice(index, 1);
|
||||
if (forward) {
|
||||
index = Math.max(0, index - 1);
|
||||
} else {
|
||||
index++;
|
||||
}
|
||||
|
||||
decorator._node.containedLayers.splice(index, 0, this.id);
|
||||
|
||||
msg = `Swapping nested layers at ${index} and ${forward ? index-1 : index+1}`;
|
||||
client.startTransaction(msg);
|
||||
decorator._updateNestedIndices();
|
||||
client.completeTransaction();
|
||||
};
|
||||
|
||||
NestedLayer.prototype.onLastNodeRemoved = function() {
|
||||
var decorator = this._parent,
|
||||
index = decorator._node.containedLayers.indexOf(this.id),
|
||||
msg = `Removing nested layer of ${decorator._node.name} at position ${index}`;
|
||||
|
||||
decorator.client.startTransaction(msg);
|
||||
decorator.client.deleteNode(this.id);
|
||||
decorator.client.completeTransaction();
|
||||
};
|
||||
this.updateNestedTerritory();
|
||||
};
|
||||
|
||||
_.extend(ContainerLayerDecorator.prototype, LayerDecorator.prototype);
|
||||
|
||||
ContainerLayerDecorator.prototype.DECORATOR_ID = DECORATOR_ID;
|
||||
|
||||
ContainerLayerDecorator.prototype._updateNestedIndices = function() {
|
||||
this._node.containedLayers.forEach((layerId, index) => {
|
||||
// Set the layer's member registry to it's index
|
||||
this.client.setMemberRegistry(
|
||||
this._node.id,
|
||||
layerId,
|
||||
CONSTANTS.CONTAINED_LAYER_SET,
|
||||
CONSTANTS.CONTAINED_LAYER_INDEX,
|
||||
index
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
ContainerLayerDecorator.prototype.addLayerAt = function(baseId, index) {
|
||||
var client = this.client,
|
||||
parentId = this._node.id,
|
||||
archNode,
|
||||
newId,
|
||||
msg;
|
||||
|
||||
// Get the index of the given layer
|
||||
index = Math.max(index, 0);
|
||||
|
||||
archNode = client.getAllMetaNodes()
|
||||
.find(node => node.getAttribute('name') === 'Architecture');
|
||||
|
||||
// Create a new Architecture node in the given node
|
||||
msg = `Adding layer to ${this._node.name} at position ${index}`;
|
||||
client.startTransaction(msg);
|
||||
|
||||
newId = client.createNode({
|
||||
parentId: parentId,
|
||||
baseId: archNode.getId()
|
||||
});
|
||||
// Create the selected layer
|
||||
client.createNode({
|
||||
parentId: newId,
|
||||
baseId: baseId
|
||||
});
|
||||
client.addMember(parentId, newId, CONSTANTS.CONTAINED_LAYER_SET);
|
||||
this._node.containedLayers.splice(index, 0, newId);
|
||||
this._updateNestedIndices();
|
||||
|
||||
client.completeTransaction();
|
||||
};
|
||||
|
||||
ContainerLayerDecorator.prototype.condense = function() {
|
||||
// hide the nested layers
|
||||
this.$el.attr('class', 'centering-offset condense');
|
||||
this.removeCreateNestedBtn();
|
||||
return LayerDecorator.prototype.condense.apply(this, arguments);
|
||||
};
|
||||
|
||||
ContainerLayerDecorator.prototype.updateNestedTerritory = function() {
|
||||
// Add the nested layers and update
|
||||
if (!this._nestedTerritoryUI) {
|
||||
this._nestedTerritoryUI = this.client.addUI(this, this._containedEvents.bind(this));
|
||||
}
|
||||
this._territory = {};
|
||||
this._node.containedLayers.forEach(id => this._territory[id] = {children: 0});
|
||||
this.client.updateTerritory(this._nestedTerritoryUI, this._territory);
|
||||
};
|
||||
|
||||
ContainerLayerDecorator.prototype._containedEvents = function(events) {
|
||||
for (var i = events.length; i--;) {
|
||||
switch (events[i].etype) {
|
||||
case GME_CONSTANTS.TERRITORY_EVENT_LOAD:
|
||||
if (!this.nestedLayers[events[i].eid]) {
|
||||
this.createNestedWidget(events[i].eid);
|
||||
}
|
||||
break;
|
||||
|
||||
case GME_CONSTANTS.TERRITORY_EVENT_UNLOAD:
|
||||
this.removeNestedWidget(events[i].eid);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (events.length > 1) { // if more than just 'complete' event
|
||||
this.updateExpand();
|
||||
}
|
||||
};
|
||||
|
||||
ContainerLayerDecorator.prototype.update = function(node) {
|
||||
var attrsUpdated = false,
|
||||
attrs = this._attributes;
|
||||
|
||||
this._node = node;
|
||||
// Update the attributes
|
||||
this.setAttributes();
|
||||
attrsUpdated = !_.isEqual(attrs, this._attributes);
|
||||
|
||||
// Check for a new nested layer
|
||||
var hasNewLayers = this._node.containedLayers
|
||||
.filter(id => !this.nestedLayers[id])
|
||||
.length > 0;
|
||||
|
||||
if (hasNewLayers) {
|
||||
this.updateNestedTerritory();
|
||||
} else {
|
||||
// Update the order of the nested layers
|
||||
if (this._selected) {
|
||||
this.expand();
|
||||
} else {
|
||||
this.condense();
|
||||
}
|
||||
}
|
||||
// Only reset fieldsWidth if the attribute has gotten larger
|
||||
if (attrsUpdated) {
|
||||
this.fieldsWidth = null;
|
||||
}
|
||||
};
|
||||
|
||||
ContainerLayerDecorator.prototype.updateExpand = function() {
|
||||
if (this.expanded) {
|
||||
this.expand();
|
||||
}
|
||||
};
|
||||
|
||||
ContainerLayerDecorator.prototype.createNestedWidget = function(id) {
|
||||
if (!this.$nested) {
|
||||
this.$nested = this.$el.append('g')
|
||||
.attr('class', 'nested-layers');
|
||||
}
|
||||
|
||||
this.nestedLayers[id] = new NestedLayer({
|
||||
$container: this.$nested,
|
||||
parent: this,
|
||||
client: this.client,
|
||||
logger: this.logger,
|
||||
onRefresh: this.onNestedRefresh,
|
||||
id: id
|
||||
});
|
||||
return this.nestedLayers[id];
|
||||
};
|
||||
|
||||
ContainerLayerDecorator.prototype.removeNestedWidget = function(id) {
|
||||
this.nestedLayers[id].destroy();
|
||||
delete this.nestedLayers[id];
|
||||
this.updateExpand();
|
||||
};
|
||||
|
||||
ContainerLayerDecorator.prototype._renderInfo = function(top, width) {
|
||||
var isAnUpdate = this.expanded,
|
||||
y = top;
|
||||
|
||||
// Add the attribute fields
|
||||
this.clearFields();
|
||||
this.$attributes = this.$el.append('g')
|
||||
.attr('fill', '#222222');
|
||||
|
||||
if (!isAnUpdate) {
|
||||
this.$attributes.attr('opacity', 0);
|
||||
}
|
||||
|
||||
y = this.createAttributeFields(y, width);
|
||||
y = this.createPointerFields(y, width);
|
||||
|
||||
if (y !== top) {
|
||||
y += this.ROW_HEIGHT/2;
|
||||
}
|
||||
return y;
|
||||
};
|
||||
|
||||
ContainerLayerDecorator.prototype.expand = function() {
|
||||
// This should be rendered with the attributes
|
||||
var height,
|
||||
width,
|
||||
|
||||
// Attributes
|
||||
initialY = 25,
|
||||
isAnUpdate = this.expanded,
|
||||
NAME_MARGIN = 15,
|
||||
nestedMargin = 15, // minimum
|
||||
margin = 5,
|
||||
y = margin + initialY,
|
||||
x = margin,
|
||||
i;
|
||||
|
||||
// Shift name down
|
||||
this.$name.attr('y', 20);
|
||||
|
||||
// Add the nested children
|
||||
var ids = this._node.containedLayers.filter(id => this.nestedLayers[id]),
|
||||
totalNestedWidth = 0,
|
||||
maxNestedHeight = 0,
|
||||
fieldWidth,
|
||||
widget;
|
||||
|
||||
if (ids.length === 0) {
|
||||
maxNestedHeight = CreateNestedBtn.SIZE * 2;
|
||||
} else {
|
||||
for (i = 0; i < ids.length; i++) {
|
||||
widget = this.nestedLayers[ids[i]].widget;
|
||||
totalNestedWidth += widget.getSvgWidth() * ZOOM;
|
||||
maxNestedHeight = Math.max(widget.getSvgHeight() * ZOOM, maxNestedHeight);
|
||||
|
||||
// Update the buttons (in case of reorder)
|
||||
this.nestedLayers[ids[i]].refreshButtons();
|
||||
}
|
||||
}
|
||||
|
||||
fieldWidth = this.fieldsWidth + 3 * NAME_MARGIN;
|
||||
width = Math.max(
|
||||
this.nameWidth + 2 * NAME_MARGIN,
|
||||
this.size.width,
|
||||
fieldWidth,
|
||||
totalNestedWidth + (ids.length + 1) * nestedMargin
|
||||
);
|
||||
|
||||
// Render attributes
|
||||
y = this._renderInfo(y, fieldWidth);
|
||||
y += nestedMargin;
|
||||
|
||||
// Update width, height
|
||||
height = y + maxNestedHeight + nestedMargin;
|
||||
|
||||
// Equally space the nested widgets
|
||||
nestedMargin = (width - totalNestedWidth)/(ids.length + 1);
|
||||
x = nestedMargin - width/2;
|
||||
for (i = 0; i < ids.length; i++) {
|
||||
this.nestedLayers[ids[i]].$el
|
||||
.attr('transform', `translate(${x}, ${y}) scale(${ZOOM})`);
|
||||
x += this.nestedLayers[ids[i]].widget.getSvgWidth() * ZOOM + nestedMargin;
|
||||
}
|
||||
|
||||
this.removeCreateNestedBtn();
|
||||
|
||||
if (ids.length === 0) {
|
||||
// Add the 'create nested layer' button if no nested layers
|
||||
this.$createNestedBtn = new CreateNestedBtn({
|
||||
context: this,
|
||||
$pEl: this.$el,
|
||||
y: y + CreateNestedBtn.SIZE
|
||||
});
|
||||
}
|
||||
|
||||
this.$body
|
||||
.transition()
|
||||
.attr('x', -width/2)
|
||||
.attr('y', 0)
|
||||
.attr('rx', 0)
|
||||
.attr('ry', 0)
|
||||
.attr('width', width)
|
||||
.attr('height', height)
|
||||
.each('end', () => {
|
||||
if (!isAnUpdate) {
|
||||
this.$attributes.attr('opacity', 1);
|
||||
this.$el.attr('class', 'centering-offset expand');
|
||||
}
|
||||
});
|
||||
|
||||
if (this.height !== height || this.width !== width) {
|
||||
this.height = height;
|
||||
this.width = width;
|
||||
this.expanded = true;
|
||||
this.$el
|
||||
.attr('transform', `translate(${this.width/2}, 0)`);
|
||||
|
||||
this.onResize();
|
||||
}
|
||||
};
|
||||
|
||||
ContainerLayerDecorator.prototype.removeCreateNestedBtn = function() {
|
||||
if (this.$createNestedBtn) {
|
||||
this.$createNestedBtn.remove();
|
||||
this.$createNestedBtn = null;
|
||||
}
|
||||
};
|
||||
|
||||
ContainerLayerDecorator.prototype.destroyNested = function() {
|
||||
Object.keys(this.nestedLayers).forEach(id => this.nestedLayers[id].destroy());
|
||||
this.nestedLayers = {};
|
||||
|
||||
if (this.$nested) {
|
||||
this.$nested.remove();
|
||||
this.$nested = this.$el.append('g')
|
||||
.attr('class', 'nested-layers');
|
||||
}
|
||||
};
|
||||
|
||||
ContainerLayerDecorator.prototype.destroy = function() {
|
||||
LayerDecorator.prototype.destroy.call(this);
|
||||
if (this._nestedTerritoryUI) {
|
||||
this.client.removeUI(this._nestedTerritoryUI);
|
||||
this._nestedTerritoryUI = null;
|
||||
}
|
||||
this.destroyNested();
|
||||
};
|
||||
|
||||
var CreateNestedBtn = function(params) {
|
||||
params.title = 'Add nested layer';
|
||||
Buttons.Add.call(this, params);
|
||||
};
|
||||
|
||||
CreateNestedBtn.SIZE = Buttons.Add.SIZE;
|
||||
CreateNestedBtn.prototype = Object.create(Buttons.Add.prototype);
|
||||
|
||||
CreateNestedBtn.prototype._onClick = function() {
|
||||
// Call addLayerAfter and prompt for a layer
|
||||
this.promptLayer()
|
||||
.then(layerId => this.addLayerAt(layerId, 0));
|
||||
};
|
||||
|
||||
ContainerLayerDecorator.prototype.expandAll = function() {
|
||||
this.expand();
|
||||
// For each of the nested layers, expand all their nodes
|
||||
Object.keys(this.nestedLayers)
|
||||
.forEach(id => this.nestedLayers[id].widget.expandAllNodes());
|
||||
};
|
||||
|
||||
ContainerLayerDecorator.prototype.condenseAll = function() {
|
||||
this.condense();
|
||||
// For each of the nested layers, expand all their nodes
|
||||
Object.keys(this.nestedLayers)
|
||||
.forEach(id => this.nestedLayers[id].widget.expandAllNodes(true));
|
||||
};
|
||||
|
||||
return ContainerLayerDecorator;
|
||||
});
|
||||
@@ -0,0 +1,175 @@
|
||||
/*globals define, _ */
|
||||
define([
|
||||
'panels/ArchEditor/ArchEditorControl',
|
||||
'widgets/ArchEditor/ArchEditorWidget',
|
||||
'widgets/EasyDAG/Buttons'
|
||||
], function(
|
||||
ArchEditor,
|
||||
ArchEditorWidget,
|
||||
Buttons
|
||||
) {
|
||||
var nop = () => {};
|
||||
var NestedLayer = function(opts) {
|
||||
this.$el = opts.$container.append('g')
|
||||
.attr('class', 'nested-layer');
|
||||
|
||||
this.id = opts.id;
|
||||
this._parent = opts.parent;
|
||||
this.logger = opts.logger;
|
||||
|
||||
this.refreshButtons = _.debounce(this.updateButtons.bind(this), 100);
|
||||
this.$outline = this.$el.append('rect') // for hover detection
|
||||
.attr('fill-opacity', 0)
|
||||
.attr('x', 0)
|
||||
.attr('y', 0);
|
||||
|
||||
this.$content = this.$el.append('g');
|
||||
this.initHover();
|
||||
|
||||
this.widget = new ArchEditorWidget({
|
||||
logger: this.logger.fork('ArchWidget'),
|
||||
autoCenter: false,
|
||||
svg: this.$content
|
||||
});
|
||||
this.widget.setTitle =
|
||||
this.widget.updateEmptyMsg = nop;
|
||||
this.onRefresh = opts.onRefresh;
|
||||
this.widget.refreshExtras = this.onWidgetRefresh.bind(this);
|
||||
|
||||
this.control = new ArchEditor({
|
||||
logger: this.logger.fork('ArchControl'),
|
||||
client: opts.client,
|
||||
embedded: true,
|
||||
widget: this.widget
|
||||
});
|
||||
this.control._onUnload = id => {
|
||||
ArchEditor.prototype._onUnload.call(this.control, id);
|
||||
// If it was the last node, remove it
|
||||
var node = this.control._client.getNode(this.id);
|
||||
if (node.getChildrenIds().length === 0) {
|
||||
this.onLastNodeRemoved();
|
||||
}
|
||||
};
|
||||
|
||||
// hack :(
|
||||
this.control.$btnModelHierarchyUp = {
|
||||
show: nop,
|
||||
hide: nop
|
||||
};
|
||||
this.widget.active = true;
|
||||
this.control.selectedObjectChanged(this.id);
|
||||
};
|
||||
|
||||
NestedLayer.prototype.initHover = function() {
|
||||
this.$hover = this.$el.append('g')
|
||||
.attr('class', 'hover-items');
|
||||
|
||||
|
||||
|
||||
this.$el.on('mouseenter', this.onHover.bind(this));
|
||||
this.$el.on('mouseleave', this.onUnhover.bind(this));
|
||||
|
||||
// Buttons
|
||||
this.$leftBtn = new Buttons.Add({
|
||||
hide: true,
|
||||
icon: this.isFirst() ? 'plus' : 'chevron-left',
|
||||
$pEl: this.$hover
|
||||
});
|
||||
|
||||
this.$rightBtn = new Buttons.Add({
|
||||
hide: true,
|
||||
icon: this.isLast() ? 'plus' : 'chevron-right',
|
||||
$pEl: this.$hover
|
||||
});
|
||||
|
||||
this.$deleteBtn = new Buttons.DeleteOne({
|
||||
hide: true,
|
||||
title: 'Delete',
|
||||
$pEl: this.$hover
|
||||
});
|
||||
|
||||
this.$leftBtn._onClick = this.clickLeft.bind(this);
|
||||
this.$rightBtn._onClick = this.clickRight.bind(this);
|
||||
this.$deleteBtn._onClick = () => this.onLastNodeRemoved();
|
||||
|
||||
this.$leftHint = this.$leftBtn.$el.append('title');
|
||||
this.$rightHint = this.$rightBtn.$el.append('title');
|
||||
this.refreshButtons();
|
||||
};
|
||||
|
||||
NestedLayer.prototype.updateButtons = function() {
|
||||
this.$leftBtn.icon = this.isFirst() ? 'plus' : 'chevron-left';
|
||||
this.$rightBtn.icon = this.isLast() ? 'plus' : 'chevron-right';
|
||||
|
||||
this.$leftHint.text(this.isFirst() ?
|
||||
'Add nested layer' :
|
||||
'Move nested layer left'
|
||||
);
|
||||
this.$rightHint.text(this.isLast() ?
|
||||
'Add nested layer' :
|
||||
'Move nested layer right'
|
||||
);
|
||||
|
||||
this.$leftBtn.render();
|
||||
this.$rightBtn.render();
|
||||
};
|
||||
|
||||
NestedLayer.prototype.clickLeft = function() {
|
||||
if (this.isFirst()) {
|
||||
this.promptLayer()
|
||||
.then(layerId => this.addLayerBefore(layerId));
|
||||
} else {
|
||||
this.moveLayerForward();
|
||||
}
|
||||
this.onUnhover();
|
||||
};
|
||||
|
||||
NestedLayer.prototype.promptLayer = function() {
|
||||
var nodes = this.widget.getValidInitialNodes();
|
||||
|
||||
return this.widget.promptLayer(nodes)
|
||||
.then(selected => selected.node.id);
|
||||
};
|
||||
|
||||
NestedLayer.prototype.clickRight = function() {
|
||||
if (this.isLast()) {
|
||||
this.promptLayer()
|
||||
.then(layerId => this.addLayerAfter(layerId));
|
||||
} else {
|
||||
this.moveLayerBackward();
|
||||
}
|
||||
this.onUnhover();
|
||||
};
|
||||
|
||||
NestedLayer.prototype.onHover = function() {
|
||||
this.refreshButtons();
|
||||
this.$hover.attr('class', 'hover-items hovered');
|
||||
};
|
||||
|
||||
NestedLayer.prototype.onUnhover = function() {
|
||||
this.$hover.attr('class', 'hover-items unhovered');
|
||||
};
|
||||
|
||||
NestedLayer.prototype.onWidgetRefresh = function() {
|
||||
var width = this.widget.getSvgWidth(),
|
||||
height = this.widget.getSvgHeight();
|
||||
|
||||
this.$outline
|
||||
.attr('width', width)
|
||||
.attr('height', height);
|
||||
|
||||
this.$leftBtn.$el.attr('transform', `translate(0, ${height/2})`);
|
||||
this.$rightBtn.$el
|
||||
.attr('transform', `translate(${width}, ${height/2})`);
|
||||
|
||||
this.onRefresh();
|
||||
};
|
||||
|
||||
NestedLayer.prototype.destroy = function() {
|
||||
this.control.destroy();
|
||||
this.widget.destroy();
|
||||
this.$el.remove();
|
||||
};
|
||||
|
||||
return NestedLayer;
|
||||
});
|
||||
@@ -47,11 +47,11 @@ define([
|
||||
// create the outputId node
|
||||
outputId = this._createOutputNode(baseId);
|
||||
} else {
|
||||
this.client.makePointer(outputId, CONSTANTS.POINTER_BASE, baseId);
|
||||
this.client.setPointer(outputId, CONSTANTS.POINTER_BASE, baseId);
|
||||
}
|
||||
// Copy the data content to the output node
|
||||
hash = target.getAttribute('data');
|
||||
this.client.setAttributes(outputId, 'data', hash);
|
||||
this.client.setAttribute(outputId, 'data', hash);
|
||||
};
|
||||
|
||||
DcOpDecorator.prototype._createOutputNode = function(baseId) {
|
||||
@@ -69,7 +69,7 @@ define([
|
||||
return metaType.getAttribute('name') === 'Outputs';
|
||||
});
|
||||
|
||||
return this.client.createChild({
|
||||
return this.client.createNode({
|
||||
baseId: baseId,
|
||||
parentId: outputCntrId
|
||||
});
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
/*globals define*/
|
||||
define([
|
||||
'decorators/EllipseDecorator/EasyDAG/AttributeField'
|
||||
], function(
|
||||
BaseAttributeField
|
||||
) {
|
||||
var AttributeField = function() {
|
||||
BaseAttributeField.apply(this, arguments);
|
||||
};
|
||||
|
||||
AttributeField.prototype = Object.create(BaseAttributeField.prototype);
|
||||
|
||||
AttributeField.prototype.onClick = function() {
|
||||
};
|
||||
|
||||
return AttributeField;
|
||||
});
|
||||
@@ -1,15 +1,17 @@
|
||||
/*globals define, _*/
|
||||
/*jshint browser: true, camelcase: false*/
|
||||
|
||||
/**
|
||||
* @author brollb / https://github.com/brollb
|
||||
*/
|
||||
|
||||
define([
|
||||
'deepforge/Constants',
|
||||
'decorators/EllipseDecorator/EasyDAG/EllipseDecorator.EasyDAGWidget',
|
||||
'./PointerField.RO',
|
||||
'./AttributeField.RO',
|
||||
'css!./JobDecorator.EasyDAGWidget.css'
|
||||
], function (
|
||||
EllipseDecorator
|
||||
CONSTANTS,
|
||||
EllipseDecorator,
|
||||
PointerField,
|
||||
AttributeField
|
||||
) {
|
||||
|
||||
'use strict';
|
||||
@@ -20,6 +22,7 @@ define([
|
||||
pending: '#9e9e9e',
|
||||
queued: '#cfd8dc',
|
||||
running: '#fff59d',
|
||||
canceled: '#ffcc80',
|
||||
success: '#66bb6a',
|
||||
fail: '#e57373'
|
||||
};
|
||||
@@ -35,6 +38,8 @@ define([
|
||||
status: true,
|
||||
execFiles: true,
|
||||
stdout: true,
|
||||
secret: true,
|
||||
jobId: true,
|
||||
debug: true
|
||||
};
|
||||
EllipseDecorator.call(this, options);
|
||||
@@ -43,18 +48,46 @@ define([
|
||||
_.extend(JobDecorator.prototype, EllipseDecorator.prototype);
|
||||
|
||||
JobDecorator.prototype.DECORATOR_ID = DECORATOR_ID;
|
||||
JobDecorator.prototype.AttributeField = AttributeField;
|
||||
JobDecorator.prototype.PointerField = PointerField;
|
||||
|
||||
JobDecorator.prototype.isInputOperation = function() {
|
||||
return this._node.name === CONSTANTS.OP.INPUT;
|
||||
};
|
||||
|
||||
JobDecorator.prototype.getDisplayName = function() {
|
||||
if (this.isInputOperation()) {
|
||||
var id = this._node.pointers.artifact;
|
||||
|
||||
// Try to look up the pointer name
|
||||
return this.nameFor[id] || this._node.name;
|
||||
}
|
||||
return this._node.name;
|
||||
};
|
||||
|
||||
JobDecorator.prototype.setAttributes = function() {
|
||||
EllipseDecorator.prototype.setAttributes.call(this);
|
||||
var attrs = this._node.attributes,
|
||||
status = attrs.status && attrs.status.value;
|
||||
status = attrs.status && attrs.status.value,
|
||||
opAttrs = Object.keys(this._node.opAttributes);
|
||||
|
||||
// Update the color based on the 'status' attr
|
||||
this.color = COLORS[status] || COLORS.fail;
|
||||
|
||||
// Set _attributes from opAttributes
|
||||
for (var i = opAttrs.length; i--;) {
|
||||
this._attributes[opAttrs[i]] = this._node.opAttributes[opAttrs[i]];
|
||||
}
|
||||
};
|
||||
|
||||
JobDecorator.prototype.updateTargetName = function() {
|
||||
EllipseDecorator.prototype.updateTargetName.apply(this, arguments);
|
||||
var name = this.getDisplayName();
|
||||
|
||||
if (name !== this.name) {
|
||||
this.name = name;
|
||||
this.onResize();
|
||||
}
|
||||
};
|
||||
|
||||
return JobDecorator;
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
/*globals define*/
|
||||
define([
|
||||
'decorators/EllipseDecorator/EasyDAG/PointerField'
|
||||
], function(
|
||||
BasePointerField
|
||||
) {
|
||||
var PointerField = function() {
|
||||
BasePointerField.apply(this, arguments);
|
||||
};
|
||||
|
||||
PointerField.prototype = Object.create(BasePointerField.prototype);
|
||||
|
||||
PointerField.prototype.onClick = function() {
|
||||
};
|
||||
|
||||
// Remove the delete icon and adjust the text location
|
||||
PointerField.prototype.hasIcon = function() {
|
||||
return false;
|
||||
};
|
||||
|
||||
return PointerField;
|
||||
});
|
||||
@@ -0,0 +1,110 @@
|
||||
/*globals define, _, WebGMEGlobal*/
|
||||
/*jshint browser: true, camelcase: false*/
|
||||
|
||||
define([
|
||||
'decorators/EllipseDecorator/EasyDAG/EllipseDecorator.EasyDAGWidget',
|
||||
'deepforge/Constants',
|
||||
'./LayerField'
|
||||
], function (
|
||||
EllipseDecorator,
|
||||
Constants,
|
||||
LayerField
|
||||
) {
|
||||
|
||||
'use strict';
|
||||
|
||||
var LayerDecorator,
|
||||
DECORATOR_ID = 'LayerDecorator';
|
||||
|
||||
// Layer nodes need to be able to...
|
||||
// - show their ports
|
||||
// - highlight ports
|
||||
// - unhighlight ports
|
||||
// - report the location of specific ports
|
||||
LayerDecorator = function (options) {
|
||||
options.skipAttributes = {name: true};
|
||||
options.skipAttributes[Constants.CTOR_ARGS_ATTR] = true;
|
||||
EllipseDecorator.call(this, options);
|
||||
};
|
||||
|
||||
_.extend(LayerDecorator.prototype, EllipseDecorator.prototype);
|
||||
|
||||
LayerDecorator.prototype.DECORATOR_ID = DECORATOR_ID;
|
||||
LayerDecorator.prototype.PointerField = LayerField;
|
||||
LayerDecorator.prototype.getDisplayName = function() {
|
||||
return this._node.name;
|
||||
};
|
||||
|
||||
// Create the pointer fields and change the event handlers
|
||||
LayerDecorator.prototype.createPointerFields = function() {
|
||||
var i = this.fields.length,
|
||||
y,
|
||||
ptr;
|
||||
|
||||
// Get the fields
|
||||
y = EllipseDecorator.prototype.createPointerFields.apply(this, arguments);
|
||||
while (i < this.fields.length) {
|
||||
// Update the event handlers
|
||||
ptr = this.fields[i].name;
|
||||
// TODO: This should be changed in EasyDAG
|
||||
this.fields[i].selectTarget = this.getValidNestedLayers.bind(this, ptr);
|
||||
i++;
|
||||
}
|
||||
return y;
|
||||
};
|
||||
|
||||
LayerDecorator.prototype.getValidNestedLayers = function(ptr) {
|
||||
var tgtId = this._node.pointers[ptr];
|
||||
if (tgtId) {
|
||||
WebGMEGlobal.State.registerActiveObject(tgtId);
|
||||
} else {
|
||||
this.createLayerArg(ptr);
|
||||
}
|
||||
};
|
||||
|
||||
LayerDecorator.prototype.createLayerArg = function(ptr) {
|
||||
// Find the Architecture node type
|
||||
var metanodes = this.client.getAllMetaNodes(),
|
||||
base = metanodes.find(node => node.getAttribute('name') === 'Architecture'),
|
||||
baseId,
|
||||
msg = `Creating layers for "${ptr}" of ${this._node.name}`,
|
||||
tgtId;
|
||||
|
||||
if (!base) {
|
||||
return this.logger.error('Could not find "Architecture" type!');
|
||||
}
|
||||
|
||||
// Create a nested "architecture" node and set the ptr target to it
|
||||
baseId = base.getId();
|
||||
this.client.startTransaction(msg);
|
||||
tgtId = this.client.createNode({
|
||||
parentId: this._node.id,
|
||||
baseId: baseId
|
||||
});
|
||||
this.client.setAttribute(tgtId, 'name', `${ptr} (${this._node.name})`);
|
||||
this.savePointer(ptr, tgtId);
|
||||
this.client.completeTransaction();
|
||||
WebGMEGlobal.State.registerActiveObject(tgtId);
|
||||
};
|
||||
|
||||
LayerDecorator.prototype.savePointer = function(ptr, to) {
|
||||
if (!to) { // delete the current target
|
||||
var currentId = this._node.pointers[ptr],
|
||||
name = this._node.name;
|
||||
|
||||
// If the target is contained in the current node, delete it!
|
||||
if (currentId.indexOf(this._node.id) === 0) {
|
||||
this.client.startTransaction(`Removing layer for ${ptr} of ${name}`);
|
||||
this.client.deleteNode(currentId);
|
||||
this.client.completeTransaction();
|
||||
this.logger.info(`Removed ${ptr} and deleted target (${currentId})`);
|
||||
} else {
|
||||
this.logger.info(`Removed ${ptr} (external architecture)`);
|
||||
}
|
||||
} else { // create and set the node
|
||||
EllipseDecorator.prototype.savePointer.apply(this, arguments);
|
||||
}
|
||||
};
|
||||
|
||||
return LayerDecorator;
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
/* globals define, _ */
|
||||
define([
|
||||
'decorators/EllipseDecorator/EasyDAG/PointerField'
|
||||
], function(
|
||||
PointerField
|
||||
) {
|
||||
// The LayerField behaves the same as PointerFields but it shows "click to view"
|
||||
// if it has a value
|
||||
var LayerField = function() {
|
||||
PointerField.apply(this, arguments);
|
||||
};
|
||||
|
||||
_.extend(LayerField.prototype, PointerField.prototype);
|
||||
|
||||
LayerField.prototype.getContent = function(content) {
|
||||
return content && 'click to view';
|
||||
};
|
||||
|
||||
LayerField.prototype.createContent = function(w, y, content) {
|
||||
PointerField.prototype.createContent.call(this, w, y, this.getContent(content));
|
||||
this.$content.attr('font-style', 'italic');
|
||||
};
|
||||
|
||||
LayerField.prototype.setValue = function(content) {
|
||||
PointerField.prototype.setValue.call(this, this.getContent(content));
|
||||
};
|
||||
|
||||
return LayerField;
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
/*globals define, _*/
|
||||
/*jshint browser: true, camelcase: false*/
|
||||
|
||||
define([
|
||||
'js/Decorators/DecoratorBase',
|
||||
'./EasyDAG/LayerDecorator.EasyDAGWidget'
|
||||
], function (
|
||||
DecoratorBase,
|
||||
LayerDecoratorEasyDAGWidget
|
||||
) {
|
||||
|
||||
'use strict';
|
||||
|
||||
var LayerDecorator,
|
||||
__parent__ = DecoratorBase,
|
||||
__parent_proto__ = DecoratorBase.prototype,
|
||||
DECORATOR_ID = 'LayerDecorator';
|
||||
|
||||
LayerDecorator = function (params) {
|
||||
var opts = _.extend({loggerName: this.DECORATORID}, params);
|
||||
|
||||
__parent__.apply(this, [opts]);
|
||||
|
||||
this.logger.debug('LayerDecorator ctor');
|
||||
};
|
||||
|
||||
_.extend(LayerDecorator.prototype, __parent_proto__);
|
||||
LayerDecorator.prototype.DECORATORID = DECORATOR_ID;
|
||||
|
||||
/*********************** OVERRIDE DecoratorBase MEMBERS **************************/
|
||||
|
||||
LayerDecorator.prototype.initializeSupportedWidgetMap = function () {
|
||||
this.supportedWidgetMap = {
|
||||
EasyDAG: LayerDecoratorEasyDAGWidget
|
||||
};
|
||||
};
|
||||
|
||||
return LayerDecorator;
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
/* globals define, _*/
|
||||
define([
|
||||
'decorators/EllipseDecorator/EasyDAG/AttributeField'
|
||||
], function(
|
||||
AttributeFieldBase
|
||||
) {
|
||||
// Attribute field in which the label is clickable and the attribute meta is editable
|
||||
var AttributeField = function() {
|
||||
AttributeFieldBase.apply(this, arguments);
|
||||
this.$label.on('click', () => this.onLabelClick());
|
||||
};
|
||||
|
||||
_.extend(AttributeField.prototype, AttributeFieldBase.prototype);
|
||||
|
||||
return AttributeField;
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
/* globals define */
|
||||
define([
|
||||
], function(
|
||||
) {
|
||||
|
||||
var CreateAttrField = function(logger, pEl, y) {
|
||||
this.$el = pEl.append('text')
|
||||
.attr('y', y)
|
||||
.attr('class', 'create-attr-field')
|
||||
.attr('text-anchor', 'middle')
|
||||
.attr('dominant-baseline', 'middle')
|
||||
.attr('font-weight', 'bold')
|
||||
.attr('font-style', 'italic')
|
||||
.text('New Attribute')
|
||||
.on('click', () => this.onClick());
|
||||
};
|
||||
|
||||
CreateAttrField.prototype.render =
|
||||
CreateAttrField.prototype.destroy = function() {};
|
||||
|
||||
CreateAttrField.prototype.width = function() {
|
||||
return this.$el[0][0].getBoundingClientRect().width;
|
||||
};
|
||||
|
||||
return CreateAttrField;
|
||||
});
|
||||
@@ -1,15 +1,17 @@
|
||||
/*globals define, $,_*/
|
||||
/*jshint browser: true, camelcase: false*/
|
||||
|
||||
/**
|
||||
* @author brollb / https://github.com/brollb
|
||||
*/
|
||||
|
||||
define([
|
||||
'decorators/EllipseDecorator/EasyDAG/EllipseDecorator.EasyDAGWidget',
|
||||
'./AttributeField',
|
||||
'./CreateAttributeField',
|
||||
'decorators/MetaDecorator/DiagramDesigner/AttributeDetailsDialog',
|
||||
'css!./OpIntDecorator.EasyDAGWidget.css'
|
||||
], function (
|
||||
DecoratorBase
|
||||
DecoratorBase,
|
||||
AttributeField,
|
||||
CreateAttributeField,
|
||||
AttributeDetailsDialog
|
||||
) {
|
||||
|
||||
'use strict';
|
||||
@@ -17,11 +19,6 @@ define([
|
||||
var OpIntDecorator,
|
||||
DECORATOR_ID = 'OpIntDecorator';
|
||||
|
||||
// OpInt nodes need to be able to...
|
||||
// - show their ports
|
||||
// - highlight ports
|
||||
// - unhighlight ports
|
||||
// - report the location of specific ports
|
||||
OpIntDecorator = function (options) {
|
||||
DecoratorBase.call(this, options);
|
||||
};
|
||||
@@ -30,14 +27,123 @@ define([
|
||||
|
||||
OpIntDecorator.prototype.DECORATOR_ID = DECORATOR_ID;
|
||||
OpIntDecorator.prototype.initialize = function() {
|
||||
if (this._node.baseName === 'Operation') {
|
||||
if (this.isOperation()) {
|
||||
this.color = '#2196f3';
|
||||
} else if (this._node.baseName) {
|
||||
// On hover, show the type
|
||||
this.enableTooltip(this._node.baseName, 'dark');
|
||||
}
|
||||
DecoratorBase.prototype.initialize.call(this);
|
||||
this.$name.on('dblclick', this.editName.bind(this));
|
||||
this.$name.on('click', () => {
|
||||
// Operations must already be selected. Otherwise, they will animate
|
||||
// after the edit name box is created and it will be placed incorrectly
|
||||
if (this.expanded || !this.isOperation()) {
|
||||
this.editName();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
OpIntDecorator.prototype.AttributeField = AttributeField;
|
||||
OpIntDecorator.prototype.isOperation = function() {
|
||||
return this._node.baseName === 'Operation';
|
||||
};
|
||||
|
||||
OpIntDecorator.prototype.createAttributeFields = function(y, width) {
|
||||
var field,
|
||||
initialY = y;
|
||||
|
||||
if (!this.isOperation()) {
|
||||
return y;
|
||||
}
|
||||
|
||||
y = DecoratorBase.prototype.createAttributeFields.call(this, y, width);
|
||||
// Change attribute field so clicking allows user to edit/delete the field
|
||||
this.fields.forEach(field =>
|
||||
field.onLabelClick = this.editAttributeMeta.bind(this, field.name));
|
||||
|
||||
// Add the 'create new attribute' field
|
||||
y += this.ROW_HEIGHT + (y === initialY ? 0 : 10);
|
||||
field = new CreateAttributeField(this.logger, this.$attributes, y, width);
|
||||
field.onClick = this.newAttribute.bind(this);
|
||||
this.fields.push(field);
|
||||
return y;
|
||||
};
|
||||
|
||||
OpIntDecorator.prototype.newAttribute = function() {
|
||||
var defSchema = {
|
||||
type: 'string'
|
||||
};
|
||||
|
||||
this.editAttributeMeta(null, defSchema);
|
||||
};
|
||||
|
||||
OpIntDecorator.prototype.expand = function() {
|
||||
DecoratorBase.prototype.expand.call(this, this.isOperation());
|
||||
};
|
||||
|
||||
OpIntDecorator.prototype.editAttributeMeta = function(name, defSchema) {
|
||||
var dialog = new AttributeDetailsDialog(),
|
||||
node = this.client.getNode(this._node.id),
|
||||
attrNames = node.getValidAttributeNames(),
|
||||
attrInfo = this._node.attributes[name] || defSchema,
|
||||
schema,
|
||||
i;
|
||||
|
||||
// Open the dialog for editing the attribute
|
||||
schema = _.extend({defaultValue: attrInfo.value}, attrInfo);
|
||||
|
||||
// Remove the current name
|
||||
i = attrNames.indexOf(name);
|
||||
if (i !== -1) {
|
||||
attrNames.splice(i, 1);
|
||||
}
|
||||
|
||||
dialog.show(schema, attrNames,
|
||||
desc => this.setAttributeMeta(name, desc),
|
||||
() => this.deleteAttribute(name));
|
||||
};
|
||||
|
||||
OpIntDecorator.prototype.deleteAttribute = function(name) {
|
||||
var opName = this._node.attributes.name.value,
|
||||
msg = `Deleting "${name}" attribute from "${opName}" operation`;
|
||||
|
||||
this.client.startTransaction(msg);
|
||||
this.client.delAttributeMeta(this._node.id, name);
|
||||
this.client.delAttribute(this._node.id, name);
|
||||
this.client.completeTransaction();
|
||||
};
|
||||
|
||||
OpIntDecorator.prototype.setAttributeMeta = function(name, desc) {
|
||||
var schema,
|
||||
opName = this._node.attributes.name.value,
|
||||
msg = `Updating "${name}" attribute in "${opName}" operation`;
|
||||
|
||||
// Create the schema from the desc
|
||||
schema = {
|
||||
type: desc.type,
|
||||
min: desc.min,
|
||||
max: desc.max,
|
||||
regexp: desc.regexp
|
||||
};
|
||||
|
||||
if (desc.isEnum) {
|
||||
schema.enum = desc.enumValues;
|
||||
}
|
||||
|
||||
// Update the operation's attribute
|
||||
this.client.startTransaction(msg);
|
||||
|
||||
if (name !== desc.name) { // Renaming attribute
|
||||
if (name) {
|
||||
this.client.delAttributeMeta(this._node.id, name);
|
||||
this.client.delAttribute(this._node.id, name);
|
||||
}
|
||||
name = desc.name;
|
||||
}
|
||||
|
||||
this.client.setAttributeMeta(this._node.id, name, schema);
|
||||
this.client.setAttribute(this._node.id, name, desc.defaultValue);
|
||||
this.client.completeTransaction();
|
||||
};
|
||||
|
||||
OpIntDecorator.prototype.editName = function() {
|
||||
@@ -89,8 +195,5 @@ define([
|
||||
return this._node.name;
|
||||
};
|
||||
|
||||
// clicking on the name should allow the user to edit it in place
|
||||
// TODO
|
||||
|
||||
return OpIntDecorator;
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/*globals define, $,_*/
|
||||
/*globals define, _*/
|
||||
/*jshint browser: true, camelcase: false*/
|
||||
|
||||
/**
|
||||
|
||||