diff --git a/.travis/check-git-clang-format-output.sh b/.travis/check-git-clang-format-output.sh
index 5f468a6c4..a6872cfc3 100755
--- a/.travis/check-git-clang-format-output.sh
+++ b/.travis/check-git-clang-format-output.sh
@@ -8,7 +8,7 @@ else
base_commit="$TRAVIS_BRANCH"
echo "Running clang-format against branch $base_commit, with hash $(git rev-parse $base_commit)"
fi
-output="$(.travis/git-clang-format --binary clang-format-3.8 --commit $base_commit --diff --exclude '(.*thirdparty/|.*redismodule.h)')"
+output="$(.travis/git-clang-format --binary clang-format-3.8 --commit $base_commit --diff --exclude '(.*thirdparty/|.*redismodule.h|.*webui*)')"
if [ "$output" == "no modified files to format" ] || [ "$output" == "clang-format did not modify any files" ] ; then
echo "clang-format passed."
exit 0
diff --git a/webui/.eslintrc.json b/webui/.eslintrc.json
new file mode 100644
index 000000000..ce13b20e9
--- /dev/null
+++ b/webui/.eslintrc.json
@@ -0,0 +1,16 @@
+{
+ "extends": ["eslint:recommended", "google"],
+ "env": {
+ "browser": true
+ },
+ "plugins": [
+ "html"
+ ],
+ "rules": {
+ "no-var": "off",
+ "new-cap": ["error", { "capIsNewExceptions": ["Polymer"] }]
+ },
+ "globals": {
+ "Polymer": true
+ }
+}
diff --git a/webui/.gitattributes b/webui/.gitattributes
new file mode 100644
index 000000000..212566614
--- /dev/null
+++ b/webui/.gitattributes
@@ -0,0 +1 @@
+* text=auto
\ No newline at end of file
diff --git a/webui/.github/ISSUE_TEMPLATE.md b/webui/.github/ISSUE_TEMPLATE.md
new file mode 100644
index 000000000..38f6726f6
--- /dev/null
+++ b/webui/.github/ISSUE_TEMPLATE.md
@@ -0,0 +1,33 @@
+
+### Description
+
+
+### Expected outcome
+
+
+
+### Actual outcome
+
+
+
+### Live Demo
+
+
+### Steps to reproduce
+
+
+
+### Browsers Affected
+
+- [ ] Chrome
+- [ ] Firefox
+- [ ] Safari 9
+- [ ] Safari 8
+- [ ] Safari 7
+- [ ] Edge
+- [ ] IE 11
+- [ ] IE 10
diff --git a/webui/.gitignore b/webui/.gitignore
new file mode 100644
index 000000000..77e759d2c
--- /dev/null
+++ b/webui/.gitignore
@@ -0,0 +1,3 @@
+bower_components/
+build/
+node_modules/
diff --git a/webui/.travis.yml b/webui/.travis.yml
new file mode 100644
index 000000000..280e8ec22
--- /dev/null
+++ b/webui/.travis.yml
@@ -0,0 +1,19 @@
+language: node_js
+sudo: required
+dist: trusty
+addons:
+ firefox: latest
+ apt:
+ sources:
+ - google-chrome
+ packages:
+ - google-chrome-stable
+node_js:
+ - '6'
+ - '5'
+ - '4'
+before_script:
+ - npm install -g bower polymer-cli
+ - bower install
+script:
+ - xvfb-run npm test
diff --git a/webui/README.md b/webui/README.md
new file mode 100644
index 000000000..6bd97d807
--- /dev/null
+++ b/webui/README.md
@@ -0,0 +1,86 @@
+# The Ray Web UI
+
+This is Ray's Web UI. It consists of two components:
+
+* The **frontend** is a [Polymer](https://www.polymer-project.org/1.0/) app that
+ uses [google-charts](https://elements.polymer-project.org/elements/google-chart)
+ for visualization.
+* The **backend** is a Python 3 websocket server (see `backend/ray_ui.py`) that
+ connects to Redis and potentially Ray.
+
+### Prerequisites
+
+The Ray Web UI requires Python 3.
+
+Install [polymer-cli](https://github.com/Polymer/polymer-cli):
+
+ pip install aioredis websockets
+ npm install -g polymer-cli
+
+### Setup
+
+The following must be done once.
+
+ cd webui
+ bower install
+
+### Start the backend
+
+First start Ray and note down the address of the Redis server. Then run
+
+ cd webui/backend
+ python ray_ui.py --redis-address 127.0.0.1:6379 --port 8888
+
+where you substitute your Redis address appropriately.
+
+### Start the frontend development server
+
+To start the front end, run the following.
+
+ cd webui
+ polymer serve --open
+
+The web UI can then be accessed at `http://localhost:8080`.
+
+### Build
+
+This command performs HTML, CSS, and JS minification on the application
+dependencies, and generates a service-worker.js file with code to pre-cache the
+dependencies based on the entrypoint and fragments specified in `polymer.json`.
+The minified files are output to the `build/unbundled` folder, and are suitable
+for serving from a HTTP/2+Push compatible server.
+
+In addition the command also creates a fallback `build/bundled` folder,
+generated using fragment bundling, suitable for serving from non
+H2/push-compatible servers or to clients that do not support H2/Push.
+
+ polymer build
+
+### Preview the build
+
+This command serves the minified version of the app at `http://localhost:8080`
+in an unbundled state, as it would be served by a push-compatible server:
+
+ polymer serve build/unbundled
+
+This command serves the minified version of the app at `http://localhost:8080`
+generated using fragment bundling:
+
+ polymer serve build/bundled
+
+### Run tests
+
+This command will run
+[Web Component Tester](https://github.com/Polymer/web-component-tester) against
+the browsers currently installed on your machine.
+
+ polymer test
+
+### Adding a new view
+
+You can extend the app by adding more views that will be demand-loaded e.g.
+based on the route, or to progressively render non-critical sections of the
+application. Each new demand-loaded fragment should be added to the list of
+`fragments` in the included `polymer.json` file. This will ensure those
+components and their dependencies are added to the list of pre-cached components
+(and will have bundles created in the fallback `bundled` build).
diff --git a/webui/backend/ray_ui.py b/webui/backend/ray_ui.py
new file mode 100644
index 000000000..89a090e8f
--- /dev/null
+++ b/webui/backend/ray_ui.py
@@ -0,0 +1,115 @@
+import aioredis
+import argparse
+import asyncio
+import binascii
+from collections import defaultdict
+import json
+import websockets
+
+parser = argparse.ArgumentParser(description="parse information for the web ui")
+parser.add_argument("--port", type=int, help="port to use for the web ui")
+parser.add_argument("--redis-address", required=True, type=str, help="the address to use for redis")
+
+loop = asyncio.get_event_loop()
+
+IDENTIFIER_LENGTH = 20
+
+def hex_identifier(identifier):
+ return binascii.hexlify(identifier).decode()
+
+def identifier(hex_identifier):
+ return binascii.unhexlify(hex_identifier)
+
+def key_to_hex_identifier(key):
+ return hex_identifier(key[(key.index(b":") + 1):(key.index(b":") + IDENTIFIER_LENGTH + 1)])
+
+def key_to_hex_identifiers(key):
+ # Extract worker_id and task_id from key of the form prefix:worker_id:task_id.
+ offset = key.index(b":") + 1
+ worker_id = hex_identifier(key[offset:(offset + IDENTIFIER_LENGTH)])
+ offset += IDENTIFIER_LENGTH + 1
+ task_id = hex_identifier(key[offset:(offset + IDENTIFIER_LENGTH)])
+ return worker_id, task_id
+
+
+async def hello(websocket, path):
+ conn = await aioredis.create_connection((redis_host, redis_port), loop=loop)
+
+ while True:
+ command = json.loads(await websocket.recv())
+ print("received command {}".format(command))
+
+ if command["command"] == "get-workers":
+ result = []
+ workers = await conn.execute("keys", "WorkerInfo:*")
+ for key in workers:
+ content = await conn.execute("hgetall", key)
+ worker_id = key_to_hex_identifier(key)
+ result.append({"worker": worker_id, "export_counter": int(content[1])})
+ await websocket.send(json.dumps(result))
+ elif command["command"] == "get-clients":
+ result = []
+ clients = await conn.execute("keys", "CL:*")
+ for key in clients:
+ content = await conn.execute("hgetall", key)
+ result.append({"client": hex_identifier(content[1]),
+ "node_ip_address": content[3].decode(),
+ "client_type": content[5].decode()})
+ await websocket.send(json.dumps(result))
+ elif command["command"] == "get-objects":
+ result = []
+ objects = await conn.execute("keys", "OI:*")
+ for key in objects:
+ content = await conn.execute("hgetall", key)
+ result.append({"object_id": hex_identifier(content[1]),
+ "hash": hex_identifier(content[3]),
+ "data_size": content[5].decode()})
+ await websocket.send(json.dumps(result))
+ elif command["command"] == "get-object-info":
+ # TODO(pcm): Get the object here (have to connect to ray) and ship content
+ # and type back to webclient. One challenge here is that the naive
+ # implementation will block the web ui backend, which is not ok if it is
+ # serving multiple users.
+ await websocket.send(json.dumps({"object_id": "none"}))
+ elif command["command"] == "get-tasks":
+ result = []
+ tasks = await conn.execute("keys", "TT:*")
+ for key in tasks:
+ content = await conn.execute("hgetall", key)
+ result.append({"task_id": key_to_hex_identifier(key),
+ "state": int(content[1]),
+ "node_id": hex_identifier(content[3])})
+ await websocket.send(json.dumps(result))
+ elif command["command"] == "get-timeline":
+ tasks = defaultdict(list)
+ for key in await conn.execute("keys", "event_log:*"):
+ worker_id, task_id = key_to_hex_identifiers(key)
+ content = await conn.execute("lrange", key, "0", "-1")
+ data = json.loads(content[0].decode())
+ begin_and_end_time = [timestamp for (timestamp, task, kind, info) in data if task == "ray:task"]
+ tasks[worker_id].append({"task_id": task_id,
+ "start_task": min(begin_and_end_time),
+ "end_task": max(begin_and_end_time)})
+ await websocket.send(json.dumps(tasks))
+ elif command["command"] == "get-events":
+ result = []
+ for key in await conn.execute("keys", "event_log:*"):
+ worker_id, task_id = key_to_hex_identifiers(key)
+ answer = await conn.execute("lrange", key, "0", "-1")
+ assert len(answer) == 1
+ events = json.loads(answer[0].decode())
+ result.extend([{"worker_id": worker_id,
+ "task_id": task_id,
+ "time": event[0],
+ "type": event[1]} for event in events])
+ await websocket.send(json.dumps(result))
+
+if __name__ == "__main__":
+ args = parser.parse_args()
+ redis_address = args.redis_address.split(":")
+ redis_host, redis_port = redis_address[0], int(redis_address[1])
+
+ start_server = websockets.serve(hello, "localhost", args.port)
+
+ loop.run_until_complete(start_server)
+ loop.run_forever()
diff --git a/webui/bower.json b/webui/bower.json
new file mode 100644
index 000000000..448b8dfff
--- /dev/null
+++ b/webui/bower.json
@@ -0,0 +1,26 @@
+{
+ "name": "polymer-starter-kit",
+ "authors": [
+ "The Polymer Authors"
+ ],
+ "license": "https://polymer.github.io/LICENSE.txt",
+ "dependencies": {
+ "app-layout": "PolymerElements/app-layout#^0.10.0",
+ "app-route": "PolymerElements/app-route#^0.9.0",
+ "iron-flex-layout": "PolymerElements/iron-flex-layout#^1.0.0",
+ "iron-icon": "PolymerElements/iron-icon#^1.0.0",
+ "iron-iconset-svg": "PolymerElements/iron-iconset-svg#^1.0.0",
+ "iron-localstorage": "PolymerElements/iron-localstorage#^1.0.0",
+ "iron-media-query": "PolymerElements/iron-media-query#^1.0.0",
+ "iron-pages": "PolymerElements/iron-pages#^1.0.0",
+ "iron-selector": "PolymerElements/iron-selector#^1.0.0",
+ "paper-icon-button": "PolymerElements/paper-icon-button#~1.1.0",
+ "polymer": "Polymer/polymer#^1.6.0",
+ "vaadin-grid": "^1.2.1",
+ "google-chart": "^1.1.1"
+ },
+ "devDependencies": {
+ "web-component-tester": "^4.0.0"
+ },
+ "private": true
+}
diff --git a/webui/client/app/index.jsx b/webui/client/app/index.jsx
deleted file mode 100644
index 0c4952978..000000000
--- a/webui/client/app/index.jsx
+++ /dev/null
@@ -1,305 +0,0 @@
-import React from 'react';
-import {render} from 'react-dom';
-import {AutoSizer, InfiniteLoader, List} from 'react-virtualized';
-import classNames from 'classnames/bind';
-import io from 'socket.io-client';
-import scrollbarSize from 'dom-helpers/util/scrollbarSize';
-
-class RayUI extends React.Component {
-
- constructor(props) {
- super(props);
- this.state = {
- table_one: "object_table",
- table_two: "task_table",
- table_three: "failure_table",
- table_four: "Remote_table",
- table_one_channel:"object",
- table_two_channel:"task",
- table_three_channel:"failure",
- table_four_channel:"remote",
- websocket_connection: io()
- };
- }
-
- render() {
- return (
-
- );
- }
-}
-class TableView extends React.Component {
-
- constructor(props) {
- super(props);
- this.state= {
- press: false
- };
- this._toggle=this._toggle.bind(this)
- }
-
- _toggle(e){
- this.setState({press: !this.state.press});
- }
-
- render() {
- return (
-
- );
- }
-}
-class TableScroll extends React.Component{
- constructor(props) {
- super(props);
- this.state = {
- messages: [],
- filteredmsg: [],
- loadedRowsMap: {},
- content: "This is the view pane.",
- atEnd: true,
- currentpos: 0,
- filter: ""
- };
- this._onfilter = this._onfilter.bind(this);
- this.filterdata = this.filterdata.bind(this);
- this._isRowLoaded = this._isRowLoaded.bind(this);
- this._rowRenderer = this._rowRenderer.bind(this);
- this._loadMoreRows = this._loadMoreRows.bind(this);
- this.objectselect = this.objectselect.bind(this);
- this._objectrenderer = this._objectrenderer.bind(this);
- this.taskselect = this.taskselect.bind(this);
- this._taskrenderer = this._taskrenderer.bind(this);
- this.computationselect = this.computationselect.bind(this);
- this._computationrenderer = this._computationrenderer.bind(this);
- this.failureselect = this.failureselect.bind(this);
- this._failurerenderer = this._failurerenderer.bind(this);
- this.remoteselect = this.remoteselect.bind(this);
- this._remoterenderer = this._remoterenderer.bind(this);
- this.scrollcontroller = this.scrollcontroller.bind(this);
- switch (this.props.channel) {
- case "object": this.renderfunction = this._objectrenderer;
- this.header = (
-
{"Object ID"}
-
{"Plasma Store ID"}
-
);
- break;
- case "failure": this.renderfunction = this._failurerenderer;
- this.header = ();
- break;
- case "computation": this.renderfunction = this._computationrenderer;
- this.header = (
-
{"Task iid"}
-
{"Function_id"}
-
);
- break;
- case "task": this.renderfunction = this._taskrenderer;
- this.header = (
-
{"Node id"}
-
{"Function_id"}
-
);
- break;
- case "remote": this.renderfunction = this._remoterenderer;
- this.header = (
-
{"Function id"}
-
{"Module"}
-
{"Function Name"}
-
);
- break;
- default: break;
- }
- }
- componentDidMount() {
- var self = this;
- console.log("port" + location.port);
- this.props.socket.emit('getall', {table:this.props.channel});
- var arr = [];
- this.props.socket.on(this.props.channel, function(message) {
- console.log("got message " + message);
- var filteredarray = self.state.filteredmsg;
- message.forEach(function(msg,i){
- // console.log("Content is " + JSON.stringify(msg));
- arr.push(msg);
- if (self.filterdata(msg, self.state.filter)) {filteredarray.push(msg);}
- });
- self.setState({messages: arr, filteredmsg: filteredarray});
- });
- }
- filterdata(data, filter) {
- console.log(data);
- var self = this;
- return filter != "" ? Object.values(data).filter(function(data){return data === Object(data) ? self.filterdata(data, filter) : String(data).includes(filter)}).length != 0 : true;
- }
- _onfilter(e) {
- var self = this;
- this.setState({filteredmsg: e.target.value != "" ? self.state.messages.filter(function(data){return self.filterdata(data, e.target.value)}) : self.state.messages, filter:e.target.value});
- }
- _isRowLoaded({ index }) {
- return !!this.state.loadedRowsMap[index];
- }
- objectselect(data, e) {
- console.log(data);
- this.setState({content:JSON.stringify(data)})
- }
- _objectrenderer(record, key, style) {
- const className = classNames("evenRow", "cell", "centeredCell");
- return (
-
- );
-
- }
- failureselect(data, e) {
- console.log(data);
- this.setState({content:data.error})
- }
- _failurerenderer(record, key, style) {
- const className = classNames("evenRow", "cell", "centeredCell");
- return (
-
- );
-
- }
- computationselect(data, e) {
- console.log(data);
- this.setState({content: JSON.stringify});
- }
-
- _computationrenderer(record, key, style) {
- const className = classNames("evenRow", "cell", "centeredCell");
- return (
-
- );
- }
- taskselect(data, e) {
- console.log(data);
- this.setState({content: JSON.stringify(data)});
- }
-
- _taskrenderer(record, key, style) {
- const className = classNames("evenRow", "cell", "centeredCell");
- return (
-
- );
-
- }
- remoteselect(data, e) {
- console.log(data);
- this.setState({content: JSON.stringify(data)});
- }
-
- _remoterenderer(record, key, style) {
- const className = classNames("evenRow", "cell", "centeredCell");
- return (
-
- );
- }
-
-
- _rowRenderer({ index, key, style, isScrolling}){
- const record = this.state.filteredmsg[index];
- return this.renderfunction(record, key, style);
- }
-
- _loadMoreRows({ startIndex, stopIndex }) {
- for (var i = startIndex; i <= stopIndex; i++) {
- this.state.loadedRowsMap[i] = 1;
- }
- let promiseResolver;
- return new Promise(resolve => {
- promiseResolver = resolve;
- })
- }
-
- scrollcontroller({clientHeight, scrollHeight, scrollTop}){
- this.setState({atEnd:scrollTop >= scrollHeight- clientHeight, currentpos: Math.floor(scrollTop/20)-1});
- }
-
- render() {
- if (!this.props.press) {
- var style = {display:'none'}
- }
- var self = this;
- return (
-
- {({ width }) => (
-
-
-
{this.header}
-
- {({ onRowsRendered, registerChild }) => (
-
- )}
-
-
-
- )}
-
- );}
-}
-render(, document.getElementById('mount-point'));
diff --git a/webui/client/index.html b/webui/client/index.html
deleted file mode 100644
index 3b77be84a..000000000
--- a/webui/client/index.html
+++ /dev/null
@@ -1,10 +0,0 @@
-
- Ray UI
-
-
-
-
-
-
-
-
diff --git a/webui/client/static/rayui.css b/webui/client/static/rayui.css
deleted file mode 100644
index 65ea4aa59..000000000
--- a/webui/client/static/rayui.css
+++ /dev/null
@@ -1,84 +0,0 @@
-.GridContainer {
- margin-top: 15px;
- border: 1px solid #e0e0e0;
-}
-
-.GridRow {
- margin-top: 15px;
- display: flex;
- flex-direction: row;
-}
-.GridColumn {
- display: flex;
- flex-direction: column;
- flex: 1 1 auto;
-}
-.LeftSideGridContainer {
- flex: 0 0 50px;
-}
-
-.BodyGrid {
- width: 100%;
- border: 1px solid #e0e0e0;
-}
-
-.evenRow,
-.oddRow {
- border-bottom: 1px solid #e0e0e0;
-}
-.oddRow {
- background-color: #fafafa;
-}
-
-.cell,
-.headerCell {
- width: 100%;
- height: 100%;
- display: flex;
- flex-direction: column;
- justify-content: center;
- padding: 0 .5em;
-}
-.cell {
- border-right: 1px solid #e0e0e0;
- border-bottom: 1px solid #e0e0e0;
- display: flex;
- flex-direction: row;
- background-color: white;
-}
-.rowbutton:hover {
- background-color: blue;
-}
-.rowbutton:hover *{
- background-color: gold;
-}
-.headerCell {
- font-weight: bold;
- border-right: 1px solid #e0e0e0;
-}
-.centeredCell {
- align-items: center;
- text-align: center;
-}
-.table {
- display: flex;
- flex-direction: row;
-}
-.letterCell {
- font-size: 1.5em;
- color: #fff;
- text-align: center;
-}
-
-.noCells {
- position: absolute;
- top: 0;
- bottom: 0;
- left: 0;
- right: 0;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 1em;
- color: #bdbdbd;
-}
diff --git a/webui/images/favicon.ico b/webui/images/favicon.ico
new file mode 100644
index 000000000..9cd897ef4
Binary files /dev/null and b/webui/images/favicon.ico differ
diff --git a/webui/images/manifest/icon-144x144.png b/webui/images/manifest/icon-144x144.png
new file mode 100644
index 000000000..c09f0ce58
Binary files /dev/null and b/webui/images/manifest/icon-144x144.png differ
diff --git a/webui/images/manifest/icon-192x192.png b/webui/images/manifest/icon-192x192.png
new file mode 100644
index 000000000..e74d22e09
Binary files /dev/null and b/webui/images/manifest/icon-192x192.png differ
diff --git a/webui/images/manifest/icon-48x48.png b/webui/images/manifest/icon-48x48.png
new file mode 100644
index 000000000..15bb11e27
Binary files /dev/null and b/webui/images/manifest/icon-48x48.png differ
diff --git a/webui/images/manifest/icon-512x512.png b/webui/images/manifest/icon-512x512.png
new file mode 100644
index 000000000..1f8053f0e
Binary files /dev/null and b/webui/images/manifest/icon-512x512.png differ
diff --git a/webui/images/manifest/icon-72x72.png b/webui/images/manifest/icon-72x72.png
new file mode 100644
index 000000000..c0af86565
Binary files /dev/null and b/webui/images/manifest/icon-72x72.png differ
diff --git a/webui/images/manifest/icon-96x96.png b/webui/images/manifest/icon-96x96.png
new file mode 100644
index 000000000..712a60533
Binary files /dev/null and b/webui/images/manifest/icon-96x96.png differ
diff --git a/webui/index.html b/webui/index.html
new file mode 100644
index 000000000..d91861d7c
--- /dev/null
+++ b/webui/index.html
@@ -0,0 +1,115 @@
+
+
+
+
+
+
+
+
+
+ Ray UI
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/webui/index.js b/webui/index.js
deleted file mode 100644
index 84ba90721..000000000
--- a/webui/index.js
+++ /dev/null
@@ -1,112 +0,0 @@
-var express = require('express');
-var app = express();
-var http = require('http').Server(app);
-var io = require('socket.io')(http);
-var redis = require("redis");
-var task = require('./task.js');
-
-if (process.argv.length > 2) {
- var port = process.argv[2];
- var sub = redis.createClient(port, {return_buffers: true});
- var db = redis.createClient(port, {return_buffers: true});
-} else {
- var sub = redis.createClient({return_buffers: true});
- var db = redis.createClient({return_buffers: true});
-}
-const assert = require('assert');
-
-app.use(express.static(__dirname + '/client'));
-app.get('/', function(req, res) {
- res.sendFile(__dirname + '/client/index.html');
-});
-
-sub.config("SET", "notify-keyspace-events", "AKE");
-sub.psubscribe("task_log:*");
-sub.psubscribe("__keyspace@0__:obj:*");
-sub.psubscribe("__keyspace@0__:Failures*");
-sub.psubscribe("__keyspace@0__:RemoteFunction*");
-io.on('connection', function(socket) {
- console.log('a user connected');
- socket.on('disconnect', function() { console.log('user disconnected'); });
- sub.on('psubscribe', function(channel, count) { console.log("Subscribed"); });
-});
-
-backlogobject = [];
-backlogtask = [];
-backlogfailures = [];
-backlogremotefunction = [];
-var failureindex;
-db.llen("Failures", function(err, result) { failureindex = result; });
-sub.on('pmessage', function(pattern, channel, message) {
- if (channel.toString().split(":")[0] === "__keyspace@0__") {
- console.log(channel.toString());
- switch (channel.toString().split(":")[1]) {
- case "Failures":
- db.lindex("Failures", failureindex++, function(err, result) {
- backlogfailures.push({
- "functionname": result.toString().split(" ")[2].slice(5, -5),
- "error": result.toString()
- });
- });
- break;
- case "obj":
- db.smembers(channel.slice(15), function(err, result) {
- console.log(result);
- backlogobject.push({
- "ObjectId": channel.slice(19).toString('hex'),
- "PlasmaStoreId": result[0].toString()
- });
- });
- break;
- case "RemoteFunction":
- db.hgetall(channel.slice(15), function(err, result) {
- backlogremotefunction.push({
- "function_id": result.function_id.toString('hex'),
- "module": result.module.toString(),
- "name": result.name.toString()
- });
- });
- break;
- default:
- console.log(channel.toString());
- break;
- }
- } else {
- backlogtask.push(task.parse_task_instance(message));
- }
-});
-
-
-
-setInterval(function() {
- if (backlogfailures.length > 0) {
- console.log("Sending ", backlogfailures.length, " objects on failure");
- console.log(backlogfailures);
- io.sockets.emit('failure', backlogfailures);
- }
- backlogfailures = [];
-}, 30);
-setInterval(function() {
- if (backlogobject.length > 0) {
- console.log("Sending ", backlogobject.length, " objects on object");
- console.log(backlogobject);
- io.sockets.emit('object', backlogobject);
- }
- backlogobject = [];
-}, 30);
-setInterval(function() {
- if (backlogtask.length > 0) {
- console.log("Sending ", backlogtask.length, " objects on task");
- io.sockets.emit('task', backlogtask);
- }
- backlogtask = [];
-}, 30);
-setInterval(function() {
- if (backlogremotefunction.length > 0) {
- console.log("Sending ", backlogremotefunction.length, " objects on remote");
- console.log(backlogremotefunction);
- io.sockets.emit('remote', backlogremotefunction);
- }
- backlogremotefunction = [];
-}, 30);
-http.listen(3000, function() { console.log('listening on *:3000'); });
diff --git a/webui/manifest.json b/webui/manifest.json
new file mode 100644
index 000000000..1c787ff45
--- /dev/null
+++ b/webui/manifest.json
@@ -0,0 +1,20 @@
+{
+ "name": "My App",
+ "short_name": "My App",
+ "start_url": "/?homescreen=1",
+ "display": "standalone",
+ "theme_color": "#3f51b5",
+ "background_color": "#3f51b5",
+ "icons": [
+ {
+ "src": "images/manifest/icon-192x192.png",
+ "sizes": "192x192",
+ "type": "image/png"
+ },
+ {
+ "src": "images/manifest/icon-512x512.png",
+ "sizes": "512x512",
+ "type": "image/png"
+ }
+ ]
+}
diff --git a/webui/package.json b/webui/package.json
index 15e00e514..f851c4de3 100644
--- a/webui/package.json
+++ b/webui/package.json
@@ -1,32 +1,13 @@
{
- "name": "RayWebUI",
- "version": "0.0.1",
- "description": "A Web UI for Ray",
- "repository": {
- "type": "git",
- "url": "git://github.com/ray-project/ray.git"
- },
- "private": true,
- "dependencies": {
- "babel-core": "~6.17.0",
- "babel-loader": "~6.2.5",
- "babel-preset-es2015": "~6.16.0",
- "babel-preset-react": "~6.16.0",
- "classnames": "~2.2.5",
- "express": "^4.10.2",
- "jbinary": "^2.1.3",
- "react": "~15.3.2",
- "react-addons-shallow-compare": "~15.3.2",
- "react-dom": "~15.3.2",
- "react-virtualized": "~8.0.12",
- "redis": "^2.6.2",
- "socket.io": "^1.5.0",
- "socket.io-client": "~1.5.0",
- "webpack": "~1.13.2",
- "dom-helpers": "~3.0.0",
- "jsesc": "~2.2.0"
- },
+ "name": "polymer-starter-kit",
+ "license": "BSD-3-Clause",
"devDependencies": {
- "webpack": "~1.13.3"
+ "eslint": "^3.12.0",
+ "eslint-config-google": "^0.7.1",
+ "eslint-plugin-html": "^1.7.0"
+ },
+ "scripts": {
+ "lint": "eslint . --ext js,html; exit 0;",
+ "test": "npm run lint && polymer test"
}
}
diff --git a/webui/polymer.json b/webui/polymer.json
new file mode 100644
index 000000000..8fe1ac481
--- /dev/null
+++ b/webui/polymer.json
@@ -0,0 +1,19 @@
+{
+ "entrypoint": "index.html",
+ "shell": "src/my-app.html",
+ "fragments": [
+ "src/my-overview.html",
+ "src/my-objects.html",
+ "src/my-view3.html",
+ "src/my-view404.html"
+ ],
+ "sourceGlobs": [
+ "src/**/*",
+ "images/**/*",
+ "bower.json"
+ ],
+ "includeDependencies": [
+ "manifest.json",
+ "bower_components/webcomponentsjs/webcomponents-lite.min.js"
+ ]
+}
diff --git a/webui/service-worker.js b/webui/service-worker.js
new file mode 100644
index 000000000..93e9d49d7
--- /dev/null
+++ b/webui/service-worker.js
@@ -0,0 +1,15 @@
+/**
+ * @license
+ * Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
+ * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
+ * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
+ * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
+ * Code distributed by Google as part of the polymer project is also
+ * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
+ */
+
+/* eslint no-console: ["error", { allow: ["info"] }] */
+
+console.info(
+ 'Service worker disabled for development, will be generated at build time.'
+);
diff --git a/webui/src/ray-app.html b/webui/src/ray-app.html
new file mode 100644
index 000000000..218873458
--- /dev/null
+++ b/webui/src/ray-app.html
@@ -0,0 +1,142 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Menu
+
+ Overview
+ Objects
+ Tasks
+ Events
+ Timeline
+
+
+
+
+
+
+
+
+
+ Ray UI
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/webui/src/ray-events.html b/webui/src/ray-events.html
new file mode 100644
index 000000000..454ca9241
--- /dev/null
+++ b/webui/src/ray-events.html
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/webui/src/ray-icons.html b/webui/src/ray-icons.html
new file mode 100644
index 000000000..fcd758c80
--- /dev/null
+++ b/webui/src/ray-icons.html
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/webui/src/ray-objects.html b/webui/src/ray-objects.html
new file mode 100644
index 000000000..d7581a13d
--- /dev/null
+++ b/webui/src/ray-objects.html
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/webui/src/ray-overview.html b/webui/src/ray-overview.html
new file mode 100644
index 000000000..7939981a7
--- /dev/null
+++ b/webui/src/ray-overview.html
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+
+
+
+
+
Overview
+
Clients
+
+
+
+
Workers
+
+
+
+
+
+
+
+
diff --git a/webui/src/ray-tasks.html b/webui/src/ray-tasks.html
new file mode 100644
index 000000000..0703f7060
--- /dev/null
+++ b/webui/src/ray-tasks.html
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/webui/src/ray-timeline.html b/webui/src/ray-timeline.html
new file mode 100644
index 000000000..2186cf5ff
--- /dev/null
+++ b/webui/src/ray-timeline.html
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+
+
+
+
+
Ray Timeline
+
+
+
+
+
+
diff --git a/webui/src/ray-view404.html b/webui/src/ray-view404.html
new file mode 100644
index 000000000..1733afba5
--- /dev/null
+++ b/webui/src/ray-view404.html
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+ Oops you hit a 404. Head back to home.
+
+
+
+
diff --git a/webui/src/shared-styles.html b/webui/src/shared-styles.html
new file mode 100644
index 000000000..5e4f2491d
--- /dev/null
+++ b/webui/src/shared-styles.html
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/webui/sw-precache-config.js b/webui/sw-precache-config.js
new file mode 100644
index 000000000..301bb56b0
--- /dev/null
+++ b/webui/sw-precache-config.js
@@ -0,0 +1,20 @@
+/**
+ * @license
+ * Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
+ * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
+ * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
+ * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
+ * Code distributed by Google as part of the polymer project is also
+ * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
+ */
+
+/* eslint-env node */
+
+module.exports = {
+ staticFileGlobs: [
+ '/index.html',
+ '/manifest.json',
+ '/bower_components/webcomponentsjs/webcomponents-lite.min.js',
+ ],
+ navigateFallback: 'index.html',
+};
diff --git a/webui/task.js b/webui/task.js
deleted file mode 100644
index bcfa95dd9..000000000
--- a/webui/task.js
+++ /dev/null
@@ -1,68 +0,0 @@
-var jb = require('jbinary');
-var stream = require('stream');
-
-var task_argument = {
- 'jBinary.littleEndian': true,
- is_ref: ['enum', 'int8', [true, false]],
- padding: ['array', 'int8', 7],
- reference: [
- 'if', 'is_ref',
- {object_id: ['array', 'uint8', 20], padding: ['array', 'int8', 4]}
- ],
- value: [
- 'if_not', 'is_ref',
- {offset: 'int64', length: 'int64', padding: ['array', 'int8', 8]}
- ]
-};
-
-var task_spec_header = {
- 'jBinary.littleEndian': true,
- function_id: ['array', 'uint8', 20],
- padding: ['array', 'uint8', 4],
- num_args: ['int64'],
- arg_index: ['int64'],
- num_returns: ['int64'],
- args_value_size: ['int64'],
- args_value_offset: ['int64'],
- arguments: ['array', task_argument, 'num_args']
-};
-
-var task_instance = {
- 'jBinary.littleEndian': true,
- task_iid: ['array', 'uint8', 20],
- state: 'int32',
- node_id: ['array', 'uint8', 20],
- padding: ['array', 'uint8', 4],
- task_spec_header: ['object', task_spec_header],
-};
-
-
-
-// Convert string or byte buffer of an object ID to hex string.
-function id_to_hex(id) {
- return new Buffer(id).toString('hex');
-}
-
-module.exports = {
- parse_task_instance: function(buffer) {
- var binary = new jb(buffer, task_instance);
- binary.read('padding');
- var task_spec = binary.read('task_spec_header');
- var arguments = [];
- for (var i = 0; i < task_spec['arguments'].length; i++) {
- var arg = task_spec['arguments'][i];
- if (arg['is_ref']) {
- console.log(arg['reference']['object_id']);
- arguments.push(id_to_hex(arg['reference']['object_id']));
- } else {
- arguments.push("value");
- }
- }
- var state = binary.read('state');
- var node_id = binary.read('node_id');
- return {
- state: state, node_id: id_to_hex(node_id),
- function_id: id_to_hex(task_spec['function_id']), arguments: arguments
- }
- }
-}
diff --git a/webui/test/.eslintrc.json b/webui/test/.eslintrc.json
new file mode 100644
index 000000000..72c940381
--- /dev/null
+++ b/webui/test/.eslintrc.json
@@ -0,0 +1,10 @@
+{
+ "env": {
+ "mocha": true
+ },
+ "globals": {
+ "assert": false,
+ "fixture": false,
+ "WCT": false
+ }
+}
diff --git a/webui/test/index.html b/webui/test/index.html
new file mode 100644
index 000000000..8678e3845
--- /dev/null
+++ b/webui/test/index.html
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+ Tests
+
+
+
+
+
+
+
diff --git a/webui/test/my-view1.html b/webui/test/my-view1.html
new file mode 100644
index 000000000..bcb79628e
--- /dev/null
+++ b/webui/test/my-view1.html
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+ my-view1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/webui/webpack.config.js b/webui/webpack.config.js
deleted file mode 100644
index 4a0acbc2c..000000000
--- a/webui/webpack.config.js
+++ /dev/null
@@ -1,19 +0,0 @@
-var webpack = require('webpack');
-var path = require('path');
-
-var BUILD_DIR = path.resolve(__dirname, 'client/public');
-var APP_DIR = path.resolve(__dirname, 'client/app');
-var config = {
- entry: APP_DIR + '/index.jsx',
- output: {path: BUILD_DIR, filename: 'bundle.js'},
- module: {
- loaders: [{
- test: /\.jsx?/,
- include: APP_DIR,
- loader: 'babel',
- query: {presets: ['react']}
- }]
- }
-};
-
-module.exports = config;