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 = (
-
{"Failed Function"}
-
); - 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 @@ + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + + + + + + + + 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 @@ + + + + + + + + + + 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 @@ + + + + + + + + + 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;