Comparar commits

...

10 Commits

Autor SHA1 Mensagem Data
Brian Broll 19272319ab v0.18.0 2016-10-17 20:32:31 -05:00
Brian Broll 60014d6411 Updated the job nodes on pipeline canceled. Fixes #843 (#844) 2016-10-15 12:33:09 -05:00
Brian Broll 5c79a68a14 Only use the joblogs API when on branch. Fixes #841 (#842) 2016-10-15 11:16:58 -05:00
Brian Broll 9d47c87006 Updated ArchEditor empty msg. Fixes #839 (#840) 2016-10-13 16:41:46 -04:00
Brian Broll e09086522c Updated node version to v6.2.1 Fixes #837 (#838) 2016-10-10 11:33:29 -05:00
Brian Broll 80318959bb Switched to klayjs for pipeline layouts. Fixes #763 (#836)
* WIP #763 basic working example

* WIP #763 applying layout position to the nodes

* WIP #763 Ignoring the klayjs lib
2016-10-09 16:19:47 -05:00
Brian Broll ee70c6c9ab Update README.md 2016-10-04 15:37:18 -05:00
Brian Broll 2eb037d800 v0.17.0 2016-10-03 20:11:33 -05:00
Brian Broll 162cefd77e Added basic worker dashboard. Fixes #759 (#835)
* WIP #759

* WIP #759 Added WorkerHeader w/ menu item

* WIP #759 Added non-func worker dialog

* WIP #759 Added updating info

* WIP #759 Fixed column header alignment

* WIP #759 Added some job queue support

* WIP #759 Improved styling of job queue

* WIP #759 Added job origins api

* WIP #759 added origin client

* WIP #759 Added originManager

* WIP #759 Added origin API,client fork support

* WIP #759 Added job queue naming

* WIP #759 Changing job queue to a table

* WIP #1759 Color coded worker list. Minor ui improvements

* WIP #759 Re-worded things

* WIP #759 Added worker id when running

* WIP #759 Fixed code climate issues

* WIP #759 Fixed code climate issues
2016-10-03 20:04:44 -05:00
Brian Broll 5fc63001f0 Added mongodb info for deepforge start 2016-10-02 17:30:42 -05:00
32 arquivos alterados com 3459 adições e 136 exclusões
+1
Ver Arquivo
@@ -34,3 +34,4 @@ exclude_paths:
- src/visualizers/widgets/TextEditor/lib/
- src/visualizers/widgets/PipelineIndex/styles/PipelineIndex.css
- src/visualizers/widgets/LineGraph/lib/
- src/visualizers/widgets/PipelineEditor/klay.js
+3 -3
Ver Arquivo
@@ -4,7 +4,7 @@
[![Join the chat at https://gitter.im/dfst/deepforge](https://badges.gitter.im/dfst/deepforge.svg)](https://gitter.im/dfst/deepforge?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Stories in Ready](https://badge.waffle.io/dfst/deepforge.png?label=ready&title=Ready)](https://waffle.io/dfst/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)!
**Notice**: DeepForge is still a work in progress and in beta! That being said, any contributions and/or feedback is greatly appreciated. If you have any questions, check out the [wiki](https://github.com/dfst/deepforge/wiki/) or drop me a line on the gitter!
# DeepForge
DeepForge is an open-source visual development environment for deep learning. Currently, it supports Convolutional Neural Networks, RNNs and LSTMs as well as the creation of custom layers. Additional features include:
@@ -28,9 +28,9 @@ Or, if you already have NodeJS (v6) installed, simply run
npm install -g deepforge
```
Next, start deepforge with `deepforge start`!
Finally, start deepforge with `deepforge start`and navigate to [http://localhost:8888](http://localhost:8888) to start using DeepForge! For more, detailed instructions, check out the [wiki](https://github.com/dfst/deepforge/wiki/Installation-Guide).
Finally, navigate to [http://localhost:8888](http://localhost:8888) to start using DeepForge! For more, detailed instructions, check out the [wiki](https://github.com/dfst/deepforge/wiki/Installation-Guide).
**Note**: running deepforge w/ `deepforge start` will also require [MongoDB](https://www.mongodb.com/download-center?jmp=nav#community) to be installed locally.
Also, be sure to check out the other available features of the `deepforge` cli; it can be used to update, manage your torch installation, uninstall deepforge and run individual components!
+2 -2
Ver Arquivo
@@ -46,8 +46,8 @@
"CHFLayout": {
"panels": [
{
"id": "Header",
"panel": "BreadcrumbHeader/BreadcrumbHeaderPanel",
"id": "WorkerHeader",
"panel": "WorkerHeader/WorkerHeaderPanel",
"container": "header",
"DEBUG_ONLY": false
},
+1
Ver Arquivo
@@ -32,6 +32,7 @@ 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';
// Visualizer descriptors
config.visualization.visualizerDescriptors.push(__dirname + '/../src/visualizers/Visualizers.json');
+4 -4
Ver Arquivo
@@ -42,10 +42,10 @@ detect_profile() {
detect_profile
set_node_version() {
# Install nodejs v6.2.0
echo "Installing NodeJS v6.2.0"
nvm install v6.2.0
nvm alias default v6.2.0
# 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
+2 -1
Ver Arquivo
@@ -12,7 +12,7 @@
"watch-test": "./node_modules/nodemon/bin/nodemon.js --exec 'node ./node_modules/mocha/bin/mocha --recursive test'",
"build-nn": "node ./utils/nn-parser.js"
},
"version": "0.16.0",
"version": "0.18.0",
"dependencies": {
"commander": "^2.9.0",
"dotenv": "^2.0.0",
@@ -20,6 +20,7 @@
"express": "^4.14.0",
"lodash.difference": "^4.1.2",
"lodash.merge": "^4.5.1",
"mongodb": "^2.2.10",
"nodemon": "^1.9.2",
"q": "1.4.1",
"rimraf": "^2.4.0",
+75
Ver Arquivo
@@ -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.info(`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;
});
@@ -1,8 +1,10 @@
/* globals define */
define([
'./APIClient',
'q',
'superagent'
], function(
APIClient,
Q,
superagent
) {
@@ -12,40 +14,22 @@ define([
var JobLogsClient = function(params) {
params = params || {};
this.logger = params.logger.fork('JobLogsClient');
// Get the server url
this.token = params.token;
this.origin = this._getServerUrl(params);
this.relativeUrl = '/execution/logs';
this.url = this.origin + this.relativeUrl;
this.logger.debug(`Setting url to ${this.url}`);
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.branch = params.branchName;
this.project = params.projectId;
this._modifiedJobs = [];
this.logger.debug(`Using <project>:<branch>: "${this.project}"/"${this.branch}"`);
this.logger.info('ctor finished');
};
JobLogsClient.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}`;
};
JobLogsClient.prototype = Object.create(APIClient.prototype);
// This method could be optimized - it could make a log of requests
JobLogsClient.prototype.fork = function(forkName) {
@@ -87,45 +71,21 @@ define([
].join('/');
};
JobLogsClient.prototype._logRequest = function(method, jobId, content) {
var deferred = Q.defer(),
req = superagent[method](this.getUrl(jobId));
this.logger.info(`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(err || res.status);
}
return deferred.resolve(res);
});
return deferred.promise;
};
JobLogsClient.prototype.appendTo = function(jobId, text) {
this._modifiedJobs.push(jobId);
this.logger.info(`Appending logs to ${jobId}`);
return this._logRequest('patch', jobId, {patch: text});
return this._request('patch', jobId, {patch: text});
};
JobLogsClient.prototype.getLog = function(jobId) {
this.logger.info(`Getting logs for ${jobId}`);
return this._logRequest('get', jobId)
return this._request('get', jobId)
.then(res => res.text);
};
JobLogsClient.prototype.deleteLog = function(jobId) {
this.logger.info(`Deleting logs for ${jobId}`);
return this._logRequest('delete', jobId);
return this._request('delete', jobId);
};
return JobLogsClient;
+60
Ver Arquivo
@@ -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;
});
+23 -14
Ver Arquivo
@@ -102,15 +102,9 @@ define([
.fail(err => this.logger.error(`Job cancel failed: ${err}`));
};
Execute.prototype.stopJob = function(job, silent) {
var jobId;
job = job || this.client.getNode(this._currentNodeId);
jobId = job.getId();
this.silentStopJob(job);
Execute.prototype._setJobStopped = function(jobId, silent) {
if (!silent) {
var name = this.client.getNode(jobId).getAttribute('name');
this.client.startTransaction(`Stopping "${name}" job`);
}
@@ -123,6 +117,16 @@ define([
}
};
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(),
@@ -165,14 +169,17 @@ define([
};
Execute.prototype._stopExecution = function(execNode, inTransaction) {
var msg = `Canceling ${execNode.getAttribute('name')} execution`;
var msg = `Canceling ${execNode.getAttribute('name')} execution`,
jobIds;
if (!inTransaction) {
this.client.startTransaction(msg);
}
this._silentStopExecution(execNode);
jobIds = this._silentStopExecution(execNode);
this.client.setAttributes(execNode.getId(), 'status', 'canceled');
jobIds.forEach(jobId => this._setJobStopped(jobId, true));
if (!inTransaction) {
this.client.completeTransaction();
@@ -180,11 +187,13 @@ define([
};
Execute.prototype._silentStopExecution = function(execNode) {
var jobIds = execNode.getChildrenIds();
var runningJobIds = execNode.getChildrenIds()
.map(id => this.client.getNode(id))
.filter(job => this.isRunning(job)); // get running jobs
jobIds.map(id => this.client.getNode(id))
.filter(job => this.isRunning(job)) // get running jobs
.forEach(job => this.silentStopJob(job)); // stop them
runningJobIds.forEach(job => this.silentStopJob(job)); // stop them
return runningJobIds;
};
return Execute;
+27 -9
Ver Arquivo
@@ -9,7 +9,8 @@ define([
'plugin/PluginBase',
'deepforge/plugin/LocalExecutor',
'deepforge/plugin/PtrCodeGen',
'deepforge/JobLogsClient',
'deepforge/api/JobLogsClient',
'deepforge/api/JobOriginClient',
'deepforge/Constants',
'deepforge/utils',
'./templates/index',
@@ -24,6 +25,7 @@ define([
LocalExecutor, // DeepForge operation primitives
PtrCodeGen,
JobLogsClient,
JobOriginClient,
CONSTANTS,
utils,
Templates,
@@ -79,14 +81,16 @@ define([
ExecuteJob.prototype.constructor = ExecuteJob;
ExecuteJob.prototype.configure = function () {
var result = PluginBase.prototype.configure.apply(this, arguments);
var result = PluginBase.prototype.configure.apply(this, arguments),
params = {
logger: this.logger,
port: this.gmeConfig.server.port,
branchName: this.branchName,
projectId: this.projectId
};
this.logManager = new JobLogsClient({
logger: this.logger,
port: this.gmeConfig.server.port,
branchName: this.branchName,
projectId: this.projectId
});
this.logManager = new JobLogsClient(params);
this.originManager = new JobOriginClient(params);
return result;
};
@@ -863,12 +867,26 @@ define([
if (info.secret) { // o.w. it is a cached job!
this.setAttribute(job, 'secret', info.secret);
}
return this.watchOperation(executor, hash, opNode, job);
return this.recordJobOrigin(hash, job);
})
.then(() => this.watchOperation(executor, hash, opNode, job))
.catch(err => this.logger.error(`Could not execute "${name}": ${err}`));
};
ExecuteJob.prototype.recordJobOrigin = function (hash, job) {
var execNode = this.core.getParent(job),
info;
info = {
hash: hash,
nodeId: this.core.getPath(job),
job: this.getAttribute(job, 'name'),
execution: this.getAttribute(execNode, 'name')
};
return this.originManager.record(hash, info);
};
ExecuteJob.prototype.createOperationFiles = function (node) {
var files = {};
// For each operation, generate the output files:
@@ -4,7 +4,6 @@
define([
'plugin/CreateExecution/CreateExecution/CreateExecution',
'plugin/ExecuteJob/ExecuteJob/ExecuteJob',
'deepforge/JobLogsClient',
'common/storage/constants',
'common/core/constants',
'q',
@@ -13,7 +12,6 @@ define([
], function (
CreateExecution,
ExecuteJob,
JobLogsClient,
STORAGE_CONSTANTS,
CONSTANTS,
Q,
@@ -118,13 +116,6 @@ define([
return callback('Current node is not a Pipeline or Execution!', this.result);
}
// Get the gmeConfig...
this.logManager = new JobLogsClient({
logger: this.logger,
port: this.gmeConfig.server.port,
branchName: this.branchName,
projectId: this.projectId
});
this._callback = callback;
this.currentForkName = null;
+3 -3
Ver Arquivo
@@ -71,7 +71,7 @@ JobLogManager.prototype.migrate = function(migrationInfo, jobIds) {
}
// Copy the job files and evaluate each of the finish functions
this.logger.info('migrating from ' + migrationInfo.srcBranch + ' to '+ migrationInfo.dstBranch);
this.logger.debug('migrating from ' + migrationInfo.srcBranch + ' to '+ migrationInfo.dstBranch);
return Q.all(jobIds.map(jobId => {
src = this._getFilePath({
project: migrationInfo.project,
@@ -112,7 +112,7 @@ JobLogManager.prototype.appendTo = function(jobInfo, logs) {
branchDirname = path.dirname(filename),
projDirname = path.dirname(branchDirname);
this.logger.info(`Appending content to ${filename}`);
this.logger.debug(`Appending content to ${filename}`);
// Make directory if needed
return this.mkdirIfNeeded(this.rootDir)
.then(() => this.mkdirIfNeeded(projDirname))
@@ -142,7 +142,7 @@ JobLogManager.prototype.delete = function(jobInfo) {
this.logger.debug(`Removing file ${filename}`);
return Q.nfcall(fs.unlink, filename);
}
this.logger.info(`${filename} doesn't exist. No need to delete...`);
this.logger.debug(`${filename} doesn't exist. No need to delete...`);
});
};
+161
Ver Arquivo
@@ -0,0 +1,161 @@
/*jshint node:true*/
/**
* This is an API to record the executor job hash to originating project, execution and job
*/
'use strict';
// This contains
var express = require('express'),
MONGO_COLLECTION = 'JobOrigins',
Q = require('q'),
router = express.Router(),
mongodb = require('mongodb');
/**
* Called when the server is created but before it starts to listening to incoming requests.
* N.B. gmeAuth, safeStorage and workerManager are not ready to use until the start function is called.
* (However inside an incoming request they are all ensured to have been initialized.)
*
* @param {object} middlewareOpts - Passed by the webgme server.
* @param {GmeConfig} middlewareOpts.gmeConfig - GME config parameters.
* @param {GmeLogger} middlewareOpts.logger - logger
* @param {function} middlewareOpts.ensureAuthenticated - Ensures the user is authenticated.
* @param {function} middlewareOpts.getUserId - If authenticated retrieves the userId from the request.
* @param {object} middlewareOpts.gmeAuth - Authorization module.
* @param {object} middlewareOpts.safeStorage - Accesses the storage and emits events (PROJECT_CREATED, COMMIT..).
* @param {object} middlewareOpts.workerManager - Spawns and keeps track of "worker" sub-processes.
*/
// When testing, use in memory storage...
function initialize(middlewareOpts) {
var logger = middlewareOpts.logger.fork('JobOriginAPI'),
gmeConfig = middlewareOpts.gmeConfig,
ensureAuthenticated = middlewareOpts.ensureAuthenticated,
REQUIRED_FIELDS = ['hash', 'project', 'execution', 'job', 'nodeId', 'branch'],
mongo;
logger.debug('initializing ...');
// Ensure authenticated can be used only after this rule.
router.use('*', function (req, res, next) {
// This header ensures that any failures with authentication won't redirect.
res.setHeader('X-WebGME-Media-Type', 'webgme.v1');
next();
});
// Use ensureAuthenticated if the routes require authentication. (Can be set explicitly for each route.)
router.use('*', ensureAuthenticated);
// Connect to mongo...
Q.nfcall(mongodb.MongoClient.connect, gmeConfig.mongo.uri, gmeConfig.mongo.options)
.then(db => {
mongo = db.collection(MONGO_COLLECTION);
logger.debug('Connected to mongo!');
})
.catch(err => {
logger.error(`Could not connect to mongo: ${err}`);
throw err;
});
router.get('/:jobHash', function (req, res/*, next*/) {
var hash = req.params.jobHash,
jobInfo = {};
mongo.findOne({hash: hash})
.then(result => {
if (result) {
// Filter the result object
for (var i = REQUIRED_FIELDS.length; i--;) {
jobInfo[REQUIRED_FIELDS[i]] = result[REQUIRED_FIELDS[i]];
}
return res.json(jobInfo);
}
res.sendStatus(404);
})
.catch(err => {
logger.error(`Storing job info failed: ${err}`);
res.status(500).send(err);
});
});
router.post('/:jobHash', function (req, res/*, next*/) {
var hash = req.params.jobHash,
jobInfo = {
hash: hash,
project: req.body.project,
execution: req.body.execution,
branch: req.body.branch,
job: req.body.job, // job name
nodeId: req.body.nodeId
};
// Check that none of the fields are undefined
logger.debug(`Storing job info for ${hash}`);
var fields = REQUIRED_FIELDS;
for (var i = fields.length; i--;) {
if (!jobInfo[fields[i]]) {
return res.status(400).send(`Missing required field: ${fields[i]}`);
}
}
return mongo.insertOne(jobInfo)
.then(() => res.sendStatus(201))
.catch(err => {
logger.error(`Storing job info failed: ${err}`);
res.status(500).send(err.toString());
});
});
router.delete('/:jobHash', function (req, res/*, next*/) {
var hash = req.params.jobHash;
mongo.findOneAndDelete({hash: hash})
.then(() => res.sendStatus(204));
});
// on fork
router.patch('/:jobHash', function (req, res) {
var hash = req.params.jobHash;
if (!req.body.branch) {
return res.status(400).send('Missing "branch" field');
}
return mongo.findOneAndUpdate({hash: hash}, {$set: {branch: req.body.branch}})
.then(() => {
logger.debug('Finished updateOne!');
res.sendStatus(200);
})
.catch(err => {
logger.error(`Job update failed: ${err}`);
res.status(500).send(err);
});
});
logger.debug('ready');
}
/**
* Called before the server starts listening.
* @param {function} callback
*/
function start(callback) {
callback();
}
/**
* Called after the server stopped listening.
* @param {function} callback
*/
function stop(callback) {
callback();
}
module.exports = {
initialize: initialize,
router: router,
start: start,
stop: stop
};
+7 -1
Ver Arquivo
@@ -100,5 +100,11 @@
"title": "ExecutionIndex",
"panel": "panels/ExecutionIndex/ExecutionIndexPanel",
"DEBUG_ONLY": false
},
{
"id": "WorkerHeader",
"title": "WorkerHeader",
"panel": "panels/WorkerHeader/WorkerHeaderPanel",
"DEBUG_ONLY": false
}
]
]
@@ -5,7 +5,7 @@
// if the job is running, get the logs from the log-storage
define([
'q',
'deepforge/JobLogsClient',
'deepforge/api/JobLogsClient',
'js/Constants',
'deepforge/Constants',
'panels/TextEditor/TextEditorControl'
@@ -82,7 +82,14 @@ define([
};
LogViewerControl.prototype._getRunningLogs = function (id) {
var logManager = new JobLogsClient({
var logManager;
if (!this._client.getActiveBranchName() || !this._client.getActiveProjectId()) {
// Logs are only stored for a given branch
return Q().then(() => '');
}
logManager = new JobLogsClient({
logger: this._logger,
projectId: this._client.getActiveProjectId(),
branchName: this._client.getActiveBranchName()
@@ -0,0 +1,106 @@
/* globals define, WebGMEGlobal */
define([
'js/Dialogs/Projects/ProjectsDialog',
'./WorkerDialog',
'js/Panels/Header/ProjectNavigatorController'
], function(
ProjectsDialog,
WorkerDialog,
GMEProjectNavigatorController
) {
'use strict';
var ProjectNavigatorController = function() {
GMEProjectNavigatorController.apply(this, arguments);
};
ProjectNavigatorController.prototype = Object.create(GMEProjectNavigatorController.prototype);
ProjectNavigatorController.prototype.initialize = function () {
var self = this,
newProject,
manageProjects,
manageWorkers;
// initialize model structure for view
self.$scope.navigator = {
items: [],
separator: true
};
manageProjects = function (/*data*/) {
var pd = new ProjectsDialog(self.gmeClient);
pd.show();
};
newProject = function (data) {
var pd = new ProjectsDialog(self.gmeClient, true, data.newType);
pd.show();
};
self.userId = WebGMEGlobal.userInfo._id;
manageWorkers = function() {
// Create the worker dialog
var pd = new WorkerDialog(self.logger);
pd.show();
};
// initialize root menu
// projects id is mandatory
if (self.config.disableProjectActions === false) {
self.root.menu = [
{
id: 'top',
items: [
{
id: 'manageProject',
label: 'Manage projects ...',
iconClass: 'glyphicon glyphicon-folder-open',
action: manageProjects,
actionData: {}
},
{
id: 'newProject',
label: 'New project ...',
disabled: WebGMEGlobal.userInfo.canCreate !== true,
iconClass: 'glyphicon glyphicon-plus',
action: newProject,
actionData: {newType: 'seed'}
},
{
id: 'importProject',
label: 'Import project ...',
disabled: WebGMEGlobal.userInfo.canCreate !== true,
iconClass: 'glyphicon glyphicon-import',
action: newProject,
actionData: {newType: 'import'}
},
{
id: 'manageWorkers',
label: 'View workers ...',
iconClass: 'glyphicon glyphicon-cloud',
action: manageWorkers
}
]
},
{
id: 'projects',
label: 'Recent projects',
totalItems: 20,
items: [],
showAllItems: manageProjects
}
];
}
self.initWithClient();
// only root is selected by default
self.$scope.navigator = {
items: self.config.disableProjectActions ? [] : [self.root],
separator: true
};
};
return ProjectNavigatorController;
});
@@ -0,0 +1,239 @@
/* globals define, $ */
define([
'q',
'superagent',
'deepforge/viz/Utils',
'deepforge/api/JobOriginClient',
'text!./WorkerModal.html',
'text!./WorkerTemplate.html.ejs',
'text!./WorkerJobItem.html',
'css!./WorkerModal.css'
], function(
Q,
superagent,
utils,
JobOriginClient,
WorkerHtml,
WorkerTemplate,
WorkerJobItem
) {
'use strict';
var WORKER_ENDPOINT = '/rest/executor/worker',
JOBS_ENDPOINT = '/rest/executor';
var WorkerDialog = function(logger) {
this.workerDict = {};
this.workers = {};
this.runningWorkers = [];
this.jobsDict = {};
this.jobs = {};
this.active = false;
this.logger = logger.fork('WorkerDialog');
this.originManager = new JobOriginClient({
logger: this.logger
});
};
WorkerDialog.prototype.initialize = function() {
this._dialog = $(WorkerHtml);
this._table = this._dialog.find('.worker-list');
this.$noJobs = this._dialog.find('.no-jobs-msg');
this.$noWorkers = this._dialog.find('.no-workers-msg');
this._isShowingJobs = false;
this._isShowingWorkers = true;
this._queue = this._dialog.find('.job-queue-list');
this._dialog.modal('show');
this._dialog.on('hidden.bs.modal', () => this.active = false);
};
WorkerDialog.prototype.show = function() {
this.active = true;
this.update();
this.initialize();
};
WorkerDialog.prototype.get = function(url) {
var deferred = Q.defer();
superagent.get(url)
.end((err, res) => {
if (err) {
return deferred.reject(err);
}
deferred.resolve(JSON.parse(res.text));
});
return deferred.promise;
};
WorkerDialog.prototype.update = function() {
// Poll the workers
return Q.all([
this.get(WORKER_ENDPOINT).then(workers => this.updateWorkers(workers)),
this.get(JOBS_ENDPOINT).then(jobs => this.updateJobs(jobs))
]).then(() => {
if (this.active) {
setTimeout(this.update.bind(this), 1000);
}
})
.catch(err => this.logger.error('Update failed:', err));
};
WorkerDialog.prototype.updateWorkers = function(workerDict) {
var ids = Object.keys(workerDict),
oldWorkerIds,
visibleWorkers = false,
i;
this.runningWorkers = [];
for (i = ids.length; i--;) {
this.updateWorker(workerDict[ids[i]]);
visibleWorkers = true;
delete this.workerDict[ids[i]];
}
this.toggleNoWorkersMsg(!visibleWorkers);
// Clear old workers
oldWorkerIds = Object.keys(this.workerDict);
for (i = oldWorkerIds.length; i--;) {
this.removeWorker(oldWorkerIds[i]);
}
this.workerDict = workerDict;
};
WorkerDialog.prototype.updateWorker = function(worker) {
var row = this.workers[worker.clientId] || $(WorkerTemplate),
clazz;
worker.lastSeen = utils.getDisplayTime(worker.lastSeen*1000);
worker.status = worker.jobs.length ? 'RUNNING' : 'READY';
clazz = worker.status === 'RUNNING' ? 'warning' : 'success';
row[0].className = clazz;
row.find('.lastSeen').text(worker.lastSeen);
row.find('.clientId').text(worker.clientId);
row.find('.status').text(worker.status);
if (!this.workers[worker.clientId]) {
this._table.append(row);
this.workers[worker.clientId] = row;
}
if (worker.status === 'RUNNING') {
this.runningWorkers.push(worker);
}
};
WorkerDialog.prototype.removeWorker = function(workerId) {
this.workers[workerId].remove();
delete this.workers[workerId];
};
WorkerDialog.prototype.updateJobs = function(jobsDict) {
var allJobIds = Object.keys(jobsDict),
hasJobs = false,
id;
this.jobsDict = jobsDict;
for (var i = allJobIds.length; i--;) {
id = allJobIds[i];
if (this.jobs[id] || !this.isFinished(id)) {
hasJobs = this.updateJobItem(id) || hasJobs;
}
}
this.setNoJobsMessage(!hasJobs); // hide if no queue
};
WorkerDialog.prototype.setNoJobsMessage = function(visible) {
var visibility = visible ? 'inherit' : 'none',
wasVisible = !this._isShowingJobs;
if (visible !== wasVisible) {
this.$noJobs.css('display', visibility);
this._isShowingJobs = !visible;
}
};
WorkerDialog.prototype.toggleNoWorkersMsg = function(visible) {
var visibility = visible ? 'inherit' : 'none';
if (visible !== this._isShowingWorkers) {
this.$noWorkers.css('display', visibility);
this._isShowingWorkers = visible;
}
};
WorkerDialog.prototype.isFinished = function(jobId) {
return this.jobsDict[jobId].status === 'FAILED_TO_EXECUTE' ||
this.jobsDict[jobId].status === 'SUCCESS' ||
this.jobsDict[jobId].status === 'CANCELED';
};
WorkerDialog.prototype.updateJobItemName = function(jobId) {
return this.originManager.getOrigin(jobId)
.then(info => {
var job = this.jobs[jobId],
project = info.project.replace(/^guest\+/, '');
if (job && this.active) {
if (info.branch !== 'master') {
project += ' (' + info.branch + ')';
}
job.find('.job-id').text(info.job);
job.find('.execution').text(info.execution);
job.find('.project').text(project);
}
});
};
WorkerDialog.prototype.getWorkerWithJob = function(jobId) {
var jobs;
for (var i = this.runningWorkers.length; i--;) {
jobs = this.runningWorkers[i].jobs;
for (var j = jobs.length; j--;) {
if (jobs[j].hash === jobId) {
return this.runningWorkers[i].clientId;
}
}
}
return 'unknown';
};
WorkerDialog.prototype.updateJobItem = function(jobId) {
var job = this.jobs[jobId] || $(WorkerJobItem),
info = this.jobsDict[jobId],
createdTime = new Date(info.createTime).getTime(),
clazz = utils.ClassForJobStatus[info.status.toLowerCase()],
status = info.status;
job[0].className = `job-tag ${clazz}`;
// Add the worker id if running
if (info.status.toLowerCase() === 'running') {
var workerId = this.getWorkerWithJob(jobId);
status += ' (' + workerId + ')';
}
job.find('.status').text(status);
if (!this.jobs[jobId]) {
job.find('.job-id').text('Loading');
job.find('.createdAt').text(utils.getDisplayTime(createdTime));
this.updateJobItemName(jobId);
this._queue.append(job);
this.jobs[jobId] = job;
}
if (this.isFinished(jobId)) {
job.remove();
delete this.jobs[jobId];
return false;
}
return true;
};
return WorkerDialog;
});
@@ -0,0 +1,41 @@
/*globals define, angular, _,*/
/*jshint browser: true*/
define([
'panels/BreadcrumbHeader/BreadcrumbHeaderPanel',
'js/Widgets/UserProfile/UserProfileWidget',
'js/Widgets/ConnectedUsers/ConnectedUsersWidget',
'js/Panels/Header/DefaultToolbar',
'panels/BreadcrumbHeader/NodePathNavigator',
'js/Toolbar/Toolbar',
'./ProjectNavigatorController'
], function (
BreadcrumbHeader,
UserProfileWidget,
ConnectedUsersWidget,
DefaultToolbar,
NodePathNavigator,
Toolbar,
ProjectNavigatorController
) {
'use strict';
var HeaderPanel;
HeaderPanel = function (layoutManager, params) {
BreadcrumbHeader.call(this, layoutManager, params);
};
//inherit from PanelBaseWithHeader
_.extend(HeaderPanel.prototype, BreadcrumbHeader.prototype);
HeaderPanel.prototype._initialize = function () {
BreadcrumbHeader.prototype._initialize.call(this);
var app = angular.module('gmeApp');
app.controller('ProjectNavigatorController', ['$scope', 'gmeClient', '$timeout', '$window', '$http',
ProjectNavigatorController]);
};
return HeaderPanel;
});
@@ -0,0 +1,7 @@
<tr>
<td class="job-id"></td>
<td class="execution">unknown</td>
<td class="project">unknown</td>
<td class="createdAt">unknown</td>
<td class="status">unknown</td>
</tr>
@@ -0,0 +1,47 @@
.worker-modal .modal-content .modal-header span {
font-size: 28px;
vertical-align: middle;
}
.worker-modal .modal-content .modal-header .header-icon {
height: 28px;
width: 28px;
margin-right: 1ex;
display: inline-block;
vertical-align: middle;
background-size: 28px 28px;
}
.worker-modal .modal-content .modal-body th {
text-align: left;
}
.worker-modal .job-tag {
margin-right: 5px;
}
.worker-modal .queue-title {
float: left;
margin-right: 5px;
font-size: 1.4em;
color: #555555;
font-weight: bold;
width: 100%;
text-align: center;
}
.worker-modal .no-jobs-msg {
font-style: italic;
font-size: 1.3em;
color: #777;
}
.worker-modal .no-workers-msg {
font-style: italic;
font-size: 1.3em;
color: #777;
}
.worker-modal .job-queue {
padding-top: 1em;
}
@@ -0,0 +1,60 @@
<div class="worker-modal modal fade in" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">x</button>
<i class="header-icon gme-icon"></i>
<span>Overview</span>
</div>
<div class="modal-body">
<div>
<div class="queue-title">Connected Workers</div>
<table class="table table-projects">
<thead>
<tr class="with-children">
<th class="title-owner">Worker Id
<!--<i class="glyphicon glyphicon-sort-by-attributes-alt sorted in-order"></i>
<i class="glyphicon glyphicon-sort-by-attributes sorted rev-order"></i>-->
</th>
<th class="title-name">Last Seen
<!--<i class="glyphicon glyphicon-sort-by-attributes-alt sorted in-order"></i>
<i class="glyphicon glyphicon-sort-by-attributes sorted rev-order"></i>-->
</th>
<th class="title-modified">Status
<!--<i class="glyphicon glyphicon-sort-by-attributes-alt sorted in-order"></i>
<i class="glyphicon glyphicon-sort-by-attributes sorted rev-order"></i>-->
</th>
</tr>
<!--<tr class="with-no-children">
<th>No projects in this group...</th>
</tr>-->
</thead>
<tbody class="worker-list">
<tr><td class="no-workers-msg">No Connected Workers...</td></tr>
</tbody>
</table>
</div>
<div class="job-queue">
<div class="queue-title">Job Queue</div>
<table class="table table-projects">
<thead>
<tr class="with-children">
<th>Job</th>
<th>Execution</th>
<th>Project</th>
<th>Creation Date</th>
<th>Status</th>
</tr>
<!--<tr class="with-no-children">-->
<!--<th>No queued jobs</th>-->
<!--</tr>-->
</thead>
<tbody class="job-queue-list">
<tr><td class="no-jobs-msg">No Running Jobs...</td></tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
@@ -0,0 +1,5 @@
<tr>
<td class="clientId"></td>
<td class="lastSeen">unknown</td>
<td class="status">unknown</td>
</tr>
@@ -28,6 +28,7 @@ define([
ArchEditorWidget = function (logger, container) {
EasyDAGWidget.call(this, logger, container);
this.$el.addClass(WIDGET_CLASS);
this._emptyMsg = 'Click to add a new layer';
};
_.extend(ArchEditorWidget.prototype, EasyDAGWidget.prototype);
@@ -17,16 +17,5 @@ define([
_.extend(Connection.prototype, EasyDAGConn.prototype);
Connection.prototype.setStartPoint = function(point) {
// Update 'this.points' to start at the given point
this.points[0] = point;
};
Connection.prototype.setEndPoint = function(point) {
// Update 'this.points' to end at the given point
var last = this.points.length-1;
this.points[last] = point;
};
return Connection;
});
@@ -1,4 +1,4 @@
/*globals WebGMEGlobal, $, define*/
/*globals WebGMEGlobal, $, define, $klay*/
/*jshint browser: true*/
define([
@@ -12,6 +12,7 @@ define([
'./Connection',
'./SelectionManager',
'underscore',
'./klay',
'css!./styles/PipelineEditorWidget.css'
], function (
CONSTANTS,
@@ -186,9 +187,6 @@ define([
this.showPorts(match.nodeId, match.portIds, isOutput)
);
// Show the 'add' button
// TODO
this.PORT_STATE = STATE.CONNECTING;
};
@@ -209,27 +207,6 @@ define([
PipelineEditorWidget.prototype.refreshExtras =
PipelineEditorWidget.prototype.updateEmptyMsg;
PipelineEditorWidget.prototype.refreshConnections = function() {
// Update the connections to they first update their start/end points
var connIds = Object.keys(this.connections),
src,
dst,
conn;
for (var i = connIds.length; i--;) {
conn = this.connections[connIds[i]];
// Update the start/end point
src = this.items[conn.src];
conn.setStartPoint(src.getPortLocation(conn.srcPort));
dst = this.items[conn.dst];
conn.setEndPoint(dst.getPortLocation(conn.dstPort, true));
conn.redraw();
}
};
//////////////////// Action Overrides ////////////////////
PipelineEditorWidget.prototype.onAddItemSelected = function(item, selected) {
@@ -443,5 +420,114 @@ define([
this.updateThumbnail(svg.outerHTML);
}, 1000);
// Changing the layout to klayjs
PipelineEditorWidget.prototype.refreshScreen = function() {
if (!this.active) {
return;
}
// WRITE UPDATES
// Update the locations of all the nodes
var graph = {
id: 'root',
properties: {
direction: 'DOWN',
'de.cau.cs.kieler.spacing': 25,
'de.cau.cs.kieler.edgeRouting': 'ORTHOGONAL'
//'de.cau.cs.kieler.klay.layered.nodePlace': 'INTERACTIVE'
},
edges: [],
children: []
};
graph.children = Object.keys(this.items).map(itemId => {
var item = this.items[itemId],
ports;
ports = item.inputs.map(p => this._getPortInfo(item, p, true))
.concat(item.outputs.map(p => this._getPortInfo(item, p)));
return {
id: itemId,
height: item.height,
width: item.width,
ports: ports,
properties: {
'de.cau.cs.kieler.portConstraints': 'FIXED_POS'
}
};
});
graph.edges = Object.keys(this.connections).map(connId => {
var conn = this.connections[connId];
return {
id: connId,
source: conn.src,
target: conn.dst,
sourcePort: conn.srcPort,
targetPort: conn.dstPort
};
});
$klay.layout({
graph: graph,
success: graph => {
this.resultGraph = graph;
this.queueFns([
this.applyLayout.bind(this, graph),
this.updateTranslation.bind(this),
this.refreshItems.bind(this),
this.refreshConnections.bind(this),
this.selectionManager.redraw.bind(this.selectionManager),
this.updateContainerWidth.bind(this),
this.refreshExtras.bind(this)
]);
}
});
};
PipelineEditorWidget.prototype._getPortInfo = function(item, port, isInput) {
var position = item.decorator.getPortLocation(port.id, isInput),
side = isInput ? 'NORTH' : 'SOUTH';
position.y += (item.height/2) - 1;
return {
id: port.id,
width: 1, // Ports are rendered outside the node in this library;
height: 1, // we want it to look like it goes right up to the node
properties: {
'de.cau.cs.kieler.portSide': side
},
x: position.x,
y: position.y
};
};
PipelineEditorWidget.prototype.applyLayout = function (graph) {
var id,
item,
lItem, // layout item
i;
for (i = graph.children.length; i--;) {
// update the x, y
lItem = graph.children[i];
id = lItem.id;
item = this.items[id];
item.x = lItem.x + item.width/2;
item.y = lItem.y + item.height/2;
}
for (i = graph.edges.length; i--;) {
// update the connection.points
lItem = graph.edges[i];
id = lItem.id;
item = this.connections[id];
item.points = lItem.bendPoints || [];
item.points.unshift(lItem.sourcePoint);
item.points.push(lItem.targetPoint);
}
};
return PipelineEditorWidget;
});
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
@@ -1,4 +1,4 @@
var testFixture = require('../globals'),
var testFixture = require('../../globals'),
expect = testFixture.expect,
assert = require('assert'),
path = testFixture.path,
@@ -7,7 +7,7 @@ var testFixture = require('../globals'),
server = testFixture.WebGME.standaloneServer(gmeConfig),
Logger = require('webgme/src/server/logger'),
logger = Logger.createWithGmeConfig('gme', gmeConfig, true),
JobLogsClient = testFixture.requirejs('deepforge/JobLogsClient'),
JobLogsClient = testFixture.requirejs('deepforge/api/JobLogsClient'),
rm_rf = require('rimraf'),
exists = require('exists-file');
+81
Ver Arquivo
@@ -0,0 +1,81 @@
var testFixture = require('../../globals'),
expect = testFixture.expect,
gmeConfig = testFixture.getGmeConfig(),
server = testFixture.WebGME.standaloneServer(gmeConfig),
Logger = require('webgme/src/server/logger'),
logger = Logger.createWithGmeConfig('gme', gmeConfig, true),
JobOriginClient = testFixture.requirejs('deepforge/api/JobOriginClient');
describe('JobOriginClient', function() {
var client = new JobOriginClient({
logger: logger,
origin: server.getUrl(),
projectId: 'testProject',
branchName: 'master'
}),
hashes = {},
getJobInfo = function() {
var hash = 'hashOrigin'+Math.ceil(Math.random()*100000);
while (hashes[hash]) {
hash = 'hashOrigin'+Math.ceil(Math.random()*100000);
}
hashes[hash] = true;
return {
hash: hash,
job: 'SomeJob',
execution: 'train_execution',
nodeId: 'K/6/1'
};
};
before(function(done) {
server.start(done);
});
after(function(done) {
server.stop(done);
});
it('should store job info', function(done) {
var job = getJobInfo();
client.record(job.hash, job)
.nodeify(done);
});
it('should read job info', function(done) {
var job = getJobInfo();
client.record(job.hash, job)
.then(() => client.getOrigin(job.hash))
.then(jobInfo => {
Object.keys(job).forEach(key => {
expect(jobInfo[key]).equal(job[key]);
});
})
.nodeify(done);
});
it('should delete job info', function(done) {
var job = getJobInfo();
client.record(job.hash, job)
.then(() => client.deleteRecord(job.hash))
.then(() => client.getOrigin(job.hash))
.then(res => expect(res).equal(null))
.nodeify(done);
});
it('should update job branch on fork', function(done) {
var job = getJobInfo(),
newBranch = 'newBranch';
client.record(job.hash, job)
.then(() => client.fork(job.hash, newBranch))
.then(() => client.getOrigin(job.hash))
.then(res => expect(res.branch).equal(newBranch))
.nodeify(done);
});
});
@@ -1,4 +1,4 @@
var testFixture = require('../globals'),
var testFixture = require('../../globals'),
superagent = testFixture.superagent,
Q = testFixture.Q,
expect = testFixture.expect,
@@ -0,0 +1,115 @@
var testFixture = require('../../globals'),
superagent = testFixture.superagent,
expect = testFixture.expect,
gmeConfig = testFixture.getGmeConfig(),
server = testFixture.WebGME.standaloneServer(gmeConfig),
mntPt = 'job/origins';
describe('JobOriginAPI', function() {
var hashes = {},
getUrl = function(hash) {
return [
server.getUrl(),
mntPt,
hash
].join('/');
},
getJobInfo = function() {
var hash = 'hash'+Math.ceil(Math.random()*100000);
while (hashes[hash]) {
hash = 'hash'+Math.ceil(Math.random()*100000);
}
hashes[hash] = true;
return {
hash: hash,
job: 'SomeJob',
branch: 'master',
execution: 'train_execution',
project: 'guest+example',
nodeId: 'K/6/1'
};
};
before(function(done) {
server.start(done);
});
after(function(done) {
server.stop(done);
});
it('should store job info', function(done) {
var job = getJobInfo();
superagent.post(getUrl(job.hash))
.send(job)
.end(function (err, res) {
expect(res.status).equal(201, err);
done();
});
});
it('should read job info', function(done) {
var job = getJobInfo(),
url = getUrl(job.hash);
superagent.post(url)
.send(job)
.end(function (err, res) {
expect(res.status).equal(201, err);
superagent.get(url)
.end((err, res) => {
var jobInfo = JSON.parse(res.text);
Object.keys(jobInfo).forEach(key => {
expect(jobInfo[key]).equal(job[key]);
});
done();
});
});
});
it('should delete job info', function(done) {
var job = getJobInfo(),
url = getUrl(job.hash);
superagent.post(url)
.send(job)
.end(function (err, res) {
expect(res.status).equal(201, err);
superagent.delete(url).end(err => {
expect(err).equal(null);
superagent.get(url)
.end((err, res) => {
expect(res.status).equal(404, err);
done();
});
});
});
});
it('should update job branch', function(done) {
var job = getJobInfo(),
url = getUrl(job.hash);
superagent.post(url) // create the job
.send(job)
.end(function (err, res) {
expect(res.status).equal(201, err);
superagent.patch(url) // update the branch
.send({branch: 'newBranch'})
.end(err => {
expect(err).equal(null);
superagent.get(url) // check the new version
.end((err, res) => {
var info = JSON.parse(res.text);
expect(info.branch).equal('newBranch');
expect(res.status).equal(200, err);
done();
});
});
});
});
});
+11
Ver Arquivo
@@ -230,6 +230,13 @@
"panel": "src/visualizers/panels/ExecutionIndex",
"secondary": false,
"widget": "src/visualizers/widgets/ExecutionIndex"
},
"WorkerHeader": {
"src": "panels/WorkerHeader/WorkerHeaderPanel",
"title": "WorkerHeader",
"panel": "src/visualizers/panels/WorkerHeader",
"secondary": false,
"widget": "src/visualizers/widgets/WorkerHeader"
}
},
"addons": {},
@@ -289,6 +296,10 @@
"JobLogsAPI": {
"src": "src/routers/JobLogsAPI",
"mount": "execution/logs"
},
"JobOriginAPI": {
"src": "src/routers/JobOriginAPI",
"mount": "job/origins"
}
}
},