New web UI. (#176)

* remove node.js webui

* temp commit

* flesh out web ui

* add documentation

* add ray timeline

* Small changes to documentation and formatting.
This commit is contained in:
Philipp Moritz
2017-01-06 00:13:22 -08:00
committed by Robert Nishihara
parent 417c04bac8
commit 33d7004914
40 changed files with 1172 additions and 627 deletions
+1 -1
View File
@@ -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
+16
View File
@@ -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
}
}
+1
View File
@@ -0,0 +1 @@
* text=auto
+33
View File
@@ -0,0 +1,33 @@
<!-- Instructions: https://github.com/PolymerElements/polymer-starter-kit/CONTRIBUTING.md#filing-issues -->
### Description
<!-- Example: The `paper-foo` element causes the page to turn pink when clicked. -->
### Expected outcome
<!-- Example: The page stays the same color. -->
### Actual outcome
<!-- Example: The page turns pink. -->
### Live Demo
<!-- Example: https://jsbin.com/cagaye/edit?html,output -->
### Steps to reproduce
<!-- Example
1. Put a `paper-foo` element in the page.
2. Open the page in a web browser.
3. Click the `paper-foo` element.
-->
### Browsers Affected
<!-- Check all that apply -->
- [ ] Chrome
- [ ] Firefox
- [ ] Safari 9
- [ ] Safari 8
- [ ] Safari 7
- [ ] Edge
- [ ] IE 11
- [ ] IE 10
+3
View File
@@ -0,0 +1,3 @@
bower_components/
build/
node_modules/
+19
View File
@@ -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
+86
View File
@@ -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).
+115
View File
@@ -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()
+26
View File
@@ -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
}
-305
View File
@@ -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 (
<div>
<TableView table={this.state.table_one} channel={this.state.table_one_channel} socket={this.state.websocket_connection}/>
<TableView table={this.state.table_two} channel={this.state.table_two_channel} socket={this.state.websocket_connection}/>
<TableView table={this.state.table_three} channel={this.state.table_three_channel} socket={this.state.websocket_connection}/>
<TableView table={this.state.table_four} channel={this.state.table_four_channel} socket={this.state.websocket_connection}/>
</div>
);
}
}
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 (
<div>
<button onClick={this._toggle}>{this.props.table}</button>
<TableScroll press={this.state.press} table={this.props.table} channel={this.props.channel} socket={this.props.socket}/>
</div>
);
}
}
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 = (<div className={classNames("evenRow", "cell", "centeredCell")}>
<div className={classNames("evenRow", "cell", "centeredCell")}>{"Object ID"}</div>
<div className={classNames("evenRow", "cell", "centeredCell")}>{"Plasma Store ID"}</div>
</div>);
break;
case "failure": this.renderfunction = this._failurerenderer;
this.header = (<div className={classNames("evenRow", "cell", "centeredCell")}>
<div className={classNames("evenRow", "cell", "centeredCell")}>{"Failed Function"}</div>
</div>);
break;
case "computation": this.renderfunction = this._computationrenderer;
this.header = (<div className={classNames("evenRow", "cell", "centeredCell")}>
<div className={classNames("evenRow", "cell", "centeredCell")}>{"Task iid"}</div>
<div className={classNames("evenRow", "cell", "centeredCell")}>{"Function_id"}</div>
</div>);
break;
case "task": this.renderfunction = this._taskrenderer;
this.header = (<div className={classNames("evenRow", "cell", "centeredCell")}>
<div className={classNames("evenRow", "cell", "centeredCell")}>{"Node id"}</div>
<div className={classNames("evenRow", "cell", "centeredCell")}>{"Function_id"}</div>
</div>);
break;
case "remote": this.renderfunction = this._remoterenderer;
this.header = (<div className={classNames("evenRow", "cell", "centeredCell")}>
<div className={classNames("evenRow", "cell", "centeredCell")}>{"Function id"}</div>
<div className={classNames("evenRow", "cell", "centeredCell")}>{"Module"}</div>
<div className={classNames("evenRow", "cell", "centeredCell")}>{"Function Name"}</div>
</div>);
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 (
<button
onClick={this.objectselect.bind(null, record)}
className={classNames("evenRow", "cell", "rowbutton", "centeredCell")}
key={key}
style={style}
>
<div className={className}>{record.ObjectId}</div>
<div className={className}>{record.PlasmaStoreId}</div>
</button>
);
}
failureselect(data, e) {
console.log(data);
this.setState({content:data.error})
}
_failurerenderer(record, key, style) {
const className = classNames("evenRow", "cell", "centeredCell");
return (
<button
onClick={this.failureselect.bind(null, record)}
className={classNames("evenRow", "cell", "rowbutton", "centeredCell")}
key={key}
style={style}
>
<div className={className}>{record.functionname}</div>
</button>
);
}
computationselect(data, e) {
console.log(data);
this.setState({content: JSON.stringify});
}
_computationrenderer(record, key, style) {
const className = classNames("evenRow", "cell", "centeredCell");
return (
<button
onClick={this.computationselect.bind(null, record)}
className={classNames("evenRow", "cell", "rowbutton", "centeredCell")}
key={key}
style={style}
>
<div className={className}>{record.task_iid.toString().substring(0,8)}</div>
<div className={className}>{record.function_id.toString().substring(0,8)}</div>
</button>
);
}
taskselect(data, e) {
console.log(data);
this.setState({content: JSON.stringify(data)});
}
_taskrenderer(record, key, style) {
const className = classNames("evenRow", "cell", "centeredCell");
return (
<button
onClick={this.taskselect.bind(null, record)}
className={classNames("evenRow", "cell", "rowbutton", "centeredCell")}
key={key}
style={style}
>
<div className={className}>{record.node_id}</div>
<div className={className}>{record.function_id.toString().substring(0,8)}</div>
</button>
);
}
remoteselect(data, e) {
console.log(data);
this.setState({content: JSON.stringify(data)});
}
_remoterenderer(record, key, style) {
const className = classNames("evenRow", "cell", "centeredCell");
return (
<button
onClick={this.remoteselect.bind(null, record)}
className={classNames("evenRow", "cell", "rowbutton", "centeredCell")}
key={key}
style={style}
>
<div className={className}>{record.function_id}</div>
<div className={className}>{record.module}</div>
<div className={className}>{record.name}</div>
</button>
);
}
_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 (
<AutoSizer disableHeight>
{({ width }) => (
<div style={style}>
<input type="text" onChange={this._onfilter} />
<div style={{width:width-2*scrollbarSize(), height:20}}>{this.header}</div>
<InfiniteLoader
isRowLoaded={this._isRowLoaded}
loadMoreRows={this._loadMoreRows}
rowCount={this.state.filteredmsg.length}
style={{display: "inline-block"}}>
{({ onRowsRendered, registerChild }) => (
<List
ref={registerChild}
rowRenderer={this._rowRenderer}
onRowsRendered={onRowsRendered}
className={"BodyGrid"}
width={width}
height={300}
onScroll={this.scrollcontroller}
overscanRowCount={20}
scrollToIndex={this.state.atEnd ? this.state.filteredmsg.length-1: this.currentpos}
scrollToAlignment={"end"}
rowCount={this.state.filteredmsg.length}
rowHeight={20}
/>
)}
</InfiniteLoader>
<textarea style={{width:width, height:300, display: "inline-block"}} value={this.state.content}></textarea>
</div>
)}
</AutoSizer>
);}
}
render(<RayUI/>, document.getElementById('mount-point'));
-10
View File
@@ -1,10 +0,0 @@
<head>
<title>Ray UI</title>
</head>
<link rel=stylesheet type=text/css href="/static/rayui.css">
<body>
<div id="mount-point"></div>
<script src="/public/bundle.js" type="text/javascript"></script>
</body>
-84
View File
@@ -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;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

+115
View File
@@ -0,0 +1,115 @@
<!--
@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
-->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="generator" content="Polymer Starter Kit">
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">
<title>Ray UI</title>
<meta name="description" content="A web UI for Ray">
<link rel="icon" href="/images/favicon.ico">
<!-- See https://goo.gl/OOhYW5 -->
<link rel="manifest" href="/manifest.json">
<!-- See https://goo.gl/qRE0vM -->
<meta name="theme-color" content="#3f51b5">
<!-- Add to homescreen for Chrome on Android. Fallback for manifest.json -->
<meta name="mobile-web-app-capable" content="yes">
<meta name="application-name" content="Ray UI">
<!-- Add to homescreen for Safari on iOS -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="Ray UI">
<!-- Homescreen icons -->
<link rel="apple-touch-icon" href="/images/manifest/icon-48x48.png">
<link rel="apple-touch-icon" sizes="72x72" href="/images/manifest/icon-72x72.png">
<link rel="apple-touch-icon" sizes="96x96" href="/images/manifest/icon-96x96.png">
<link rel="apple-touch-icon" sizes="144x144" href="/images/manifest/icon-144x144.png">
<link rel="apple-touch-icon" sizes="192x192" href="/images/manifest/icon-192x192.png">
<!-- Tile icon for Windows 8 (144x144 + tile color) -->
<meta name="msapplication-TileImage" content="/images/manifest/icon-144x144.png">
<meta name="msapplication-TileColor" content="#3f51b5">
<meta name="msapplication-tap-highlight" content="no">
<script>
// Setup Polymer options
window.Polymer = {
dom: 'shadow',
lazyRegister: true,
};
// Load webcomponentsjs polyfill if browser does not support native
// Web Components
(function() {
'use strict';
var onload = function() {
// For native Imports, manually fire WebComponentsReady so user code
// can use the same code path for native and polyfill'd imports.
if (!window.HTMLImports) {
document.dispatchEvent(
new CustomEvent('WebComponentsReady', {bubbles: true})
);
}
};
var webComponentsSupported = (
'registerElement' in document
&& 'import' in document.createElement('link')
&& 'content' in document.createElement('template')
);
if (!webComponentsSupported) {
var script = document.createElement('script');
script.async = true;
script.src = '/bower_components/webcomponentsjs/webcomponents-lite.min.js';
script.onload = onload;
document.head.appendChild(script);
} else {
onload();
}
})();
// Load pre-caching Service Worker
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/service-worker.js');
});
}
</script>
<link rel="import" href="/src/ray-app.html">
<link rel="import" href="/bower_components/google-chart/google-chart.html">
<link rel="import" href="/bower_components/vaadin-grid/vaadin-grid.html">
<style>
body {
margin: 0;
font-family: 'Roboto', 'Noto', sans-serif;
line-height: 1.5;
min-height: 100vh;
background-color: #eeeeee;
}
</style>
</head>
<body>
<ray-app></ray-app>
<!-- Built with love using Polymer Starter Kit -->
</body>
</html>
-112
View File
@@ -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'); });
+20
View File
@@ -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"
}
]
}
+9 -28
View File
@@ -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"
}
}
+19
View File
@@ -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"
]
}
+15
View File
@@ -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.'
);
+142
View File
@@ -0,0 +1,142 @@
<!--
@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
-->
<link rel="import" href="../bower_components/polymer/polymer.html">
<link rel="import" href="../bower_components/app-layout/app-drawer/app-drawer.html">
<link rel="import" href="../bower_components/app-layout/app-drawer-layout/app-drawer-layout.html">
<link rel="import" href="../bower_components/app-layout/app-header/app-header.html">
<link rel="import" href="../bower_components/app-layout/app-header-layout/app-header-layout.html">
<link rel="import" href="../bower_components/app-layout/app-scroll-effects/app-scroll-effects.html">
<link rel="import" href="../bower_components/app-layout/app-toolbar/app-toolbar.html">
<link rel="import" href="../bower_components/app-route/app-location.html">
<link rel="import" href="../bower_components/app-route/app-route.html">
<link rel="import" href="../bower_components/iron-pages/iron-pages.html">
<link rel="import" href="../bower_components/iron-selector/iron-selector.html">
<link rel="import" href="../bower_components/paper-icon-button/paper-icon-button.html">
<link rel="import" href="ray-icons.html">
<dom-module id="ray-app">
<template>
<style>
:host {
--app-primary-color: #4285f4;
--app-secondary-color: black;
display: block;
}
app-header {
color: #fff;
background-color: var(--app-primary-color);
}
app-header paper-icon-button {
--paper-icon-button-ink-color: white;
}
.drawer-list {
margin: 0 20px;
}
.drawer-list a {
display: block;
padding: 0 16px;
text-decoration: none;
color: var(--app-secondary-color);
line-height: 40px;
}
.drawer-list a.iron-selected {
color: black;
font-weight: bold;
}
</style>
<app-location route="{{route}}"></app-location>
<app-route
route="{{route}}"
pattern="/:page"
data="{{routeData}}"
tail="{{subroute}}"></app-route>
<app-drawer-layout fullbleed>
<!-- Drawer content -->
<app-drawer id="drawer">
<app-toolbar>Menu</app-toolbar>
<iron-selector selected="[[page]]" attr-for-selected="name" class="drawer-list" role="navigation">
<a name="overview" href="/overview">Overview</a>
<a name="objects" href="/objects">Objects</a>
<a name="tasks" href="/tasks">Tasks</a>
<a name="events" href="/events">Events</a>
<a name="timeline" href="/timeline">Timeline</a>
</iron-selector>
</app-drawer>
<!-- Main content -->
<app-header-layout has-scrolling-region>
<app-header condenses reveals effects="waterfall">
<app-toolbar>
<paper-icon-button icon="ray-icons:menu" drawer-toggle></paper-icon-button>
<div main-title>Ray UI</div>
</app-toolbar>
</app-header>
<iron-pages
selected="[[page]]"
attr-for-selected="name"
fallback-selection="view404"
role="main">
<ray-overview name="overview"></ray-overview>
<ray-objects name="objects"></ray-objects>
<ray-tasks name="tasks"></ray-tasks>
<ray-events name="events"></ray-events>
<ray-timeline name="timeline"></ray-timeline>
<ray-view404 name="view404"></ray-view404>
</iron-pages>
</app-header-layout>
</app-drawer-layout>
</template>
<script>
Polymer({
is: 'ray-app',
properties: {
page: {
type: String,
reflectToAttribute: true,
observer: '_pageChanged',
},
},
observers: [
'_routePageChanged(routeData.page)',
],
_routePageChanged: function(page) {
this.page = page || 'overview';
if (!this.$.drawer.persistent) {
this.$.drawer.close();
}
},
_pageChanged: function(page) {
// Load page import on demand. Show 404 page if fails
var resolvedPageUrl = this.resolveUrl('ray-' + page + '.html');
this.importHref(resolvedPageUrl, null, this._showPage404, true);
},
_showPage404: function() {
this.page = 'view404';
},
});
</script>
</dom-module>
+57
View File
@@ -0,0 +1,57 @@
<!--
@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
-->
<link rel="import" href="../bower_components/polymer/polymer.html">
<link rel="import" href="shared-styles.html">
<dom-module id="ray-events">
<template>
<style include="shared-styles">
:host {
display: block;
padding: 10px;
}
</style>
<div class="card">
<h1>Events</h1>
<vaadin-grid id="events">
<table>
<colgroup>
<col name="worker_id" sortable="" sort-direction="desc"/>
<col name="task_id" sortable="" sort-direction="desc"/>
<col name="time" sortable="" sort-direction="desc"/>
<col name="type" sortable="" sort-direction="desc"/>
</colgroup>
</table>
</vaadin-grid>
</div>
</template>
<script>
var backend_address = "ws://127.0.0.1:8888";
Polymer({
is: 'ray-events',
ready: function() {
var eventSocket = new WebSocket(backend_address);
var events = Polymer.dom(this.root).querySelector("#events");
eventSocket.onopen = function() {
eventSocket.send(JSON.stringify({"command": "get-events"}));
}
eventSocket.onmessage = function(answer) {
console.dir(answer.data);
events.items = JSON.parse(answer.data);
}
}
});
</script>
</dom-module>
+31
View File
@@ -0,0 +1,31 @@
<!--
@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
-->
<link rel="import" href="../bower_components/iron-icon/iron-icon.html">
<link rel="import" href="../bower_components/iron-iconset-svg/iron-iconset-svg.html">
<iron-iconset-svg name="my-icons" size="24">
<svg>
<defs>
<g id="arrow-back">
<path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z" />
</g>
<g id="menu">
<path d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z" />
</g>
<g id="chevron-right">
<path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z" />
</g>
<g id="close">
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" />
</g>
</defs>
</svg>
</iron-iconset-svg>
+67
View File
@@ -0,0 +1,67 @@
<!--
@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
-->
<link rel="import" href="../bower_components/polymer/polymer.html">
<link rel="import" href="shared-styles.html">
<dom-module id="ray-objects">
<template>
<style include="shared-styles">
:host {
display: block;
padding: 10px;
}
</style>
<div class="card">
<h1>Objects</h1>
<vaadin-grid id="objects">
<table>
<colgroup>
<col name="object_id" sortable="" sort-direction="desc"/>
<col name="data_size" sortable="" sort-direction="desc"/>
<col name="hash" sortable="" sort-direction="desc"/>
</colgroup>
</table>
</vaadin-grid>
</div>
</template>
<script>
var backend_address = "ws://127.0.0.1:8888";
Polymer({
is: 'ray-objects',
ready: function() {
var objectSocket = new WebSocket(backend_address);
var objectInfoSocket = new WebSocket(backend_address);
var objects = Polymer.dom(this.root).querySelector("#objects");
objectSocket.onopen = function() {
objectSocket.send(JSON.stringify({"command": "get-objects"}));
}
objectSocket.onmessage = function(answer) {
objects.items = JSON.parse(answer.data);
}
objects.addEventListener('selected-items-changed', function() {
var index = this.selection.selected();
if (index != undefined && this.items != undefined && this.items[index] != undefined) {
var id = this.items[index]["object_id"];
objectInfoSocket.send(JSON.stringify({"command": "get-object-info", "object_id": id}));
objectInfoSocket.onmessage = function(answer) {
console.log(answer.data);
}
}
}.bind(objects));
}
});
</script>
</dom-module>
+75
View File
@@ -0,0 +1,75 @@
<!--
@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
-->
<link rel="import" href="../bower_components/polymer/polymer.html">
<link rel="import" href="shared-styles.html">
<dom-module id="ray-overview">
<template>
<style include="shared-styles">
:host {
display: block;
padding: 10px;
}
</style>
<div class="card">
<h1>Overview</h1>
<h2>Clients</h2>
<vaadin-grid id="clients">
<table>
<colgroup>
<col name="client_type" sortable="" sort-direction="desc"/>
<col name="client" sortable="" sort-direction="desc"/>
<col name="node_ip_address" sortable="" sort-direction="desc"/>
</colgroup>
</table>
</vaadin-grid>
<h2>Workers</h2>
<vaadin-grid id="workers">
<table>
<colgroup>
<col name="worker" sortable="" sort-direction="desc"/>
<col name="export_counter" sortable="" sort-direction="desc"/>
</colgroup>
</table>
</vaadin-grid>
</div>
</template>
<script>
var backend_address = "ws://127.0.0.1:8888";
Polymer({
is: 'ray-overview',
ready: function() {
var workerSocket = new WebSocket(backend_address);
var workers = Polymer.dom(this.root).querySelector("#workers");
workerSocket.onopen = function() {
workerSocket.send(JSON.stringify({"command": "get-workers"}));
}
workerSocket.onmessage = function(answer) {
workers.items = JSON.parse(answer.data);
}
var clientSocket = new WebSocket(backend_address);
var clients = Polymer.dom(this.root).querySelector("#clients");
clientSocket.onopen = function() {
clientSocket.send(JSON.stringify({"command": "get-clients"}));
}
clientSocket.onmessage = function(answer) {
var result = JSON.parse(answer.data);
console.dir(result);
clients.items = result;
}
}
});
</script>
</dom-module>
+55
View File
@@ -0,0 +1,55 @@
<!--
@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
-->
<link rel="import" href="../bower_components/polymer/polymer.html">
<link rel="import" href="shared-styles.html">
<dom-module id="ray-tasks">
<template>
<style include="shared-styles">
:host {
display: block;
padding: 10px;
}
</style>
<div class="card">
<h1>Tasks</h1>
<vaadin-grid id="tasks">
<table>
<colgroup>
<col name="task_id" sortable="" sort-direction="desc"/>
<col name="state" sortable="" sort-direction="desc"/>
<col name="node_id" sortable="" sort-direction="desc"/>
</colgroup>
</table>
</vaadin-grid>
</div>
</template>
<script>
var backend_address = "ws://127.0.0.1:8888";
Polymer({
is: 'ray-tasks',
ready: function() {
var taskSocket = new WebSocket(backend_address);
var tasks = Polymer.dom(this.root).querySelector("#tasks");
taskSocket.onopen = function() {
taskSocket.send(JSON.stringify({"command": "get-tasks"}));
}
taskSocket.onmessage = function(answer) {
tasks.items = JSON.parse(answer.data);
}
}
});
</script>
</dom-module>
+81
View File
@@ -0,0 +1,81 @@
<!--
@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
-->
<link rel="import" href="../bower_components/polymer/polymer.html">
<link rel="import" href="shared-styles.html">
<dom-module id="ray-timeline">
<template>
<style include="shared-styles">
:host {
display: block;
padding: 10px;
}
google-chart {
height: 600px;
width: 95%;
}
</style>
<div class="card">
<h1>Ray Timeline</h1>
<figure id="timeline-container"></figure>
</div>
</template>
<script>
var redis_address = "ws://127.0.0.1:8888";
Polymer({
is: 'ray-timeline',
ready: function() {
var loader = document.createElement('google-chart-loader');
var timelineContainer = Polymer.dom(this.root).querySelector('figure#timeline-container');
var timelineChart = document.createElement('google-chart');
timelineChart.setAttribute('type', 'timeline');
timelineChart.options = {
height: 400,
gantt: {
trackHeight: 30
}
};
timelineContainer.appendChild(timelineChart);
loader.dataTable().then(function(table) {
table.addColumn({ type: 'string', id: 'Worker' });
table.addColumn({ type: 'string', id: 'Task'});
table.addColumn({ type: 'date', id: 'Start' });
table.addColumn({ type: 'date', id: 'End' });
var socket = new WebSocket(redis_address);
socket.onopen = function() {
socket.send(JSON.stringify({"command": "get-timeline"}));
}
socket.onmessage = function(messageEvent) {
var tasks = JSON.parse(messageEvent.data);
for (var key in tasks) {
for (var index in tasks[key]) {
var task = tasks[key][index];
var d1 = new Date(task["start_task"] * 1000 * 1000);
var d2 = new Date(task["end_task"] * 1000 * 1000);
table.addRows([[key, task["task_id"], d1, d2]]);
}
}
console.dir(table);
timelineChart.data = table;
}
});
}
});
</script>
</dom-module>
+35
View File
@@ -0,0 +1,35 @@
<!--
@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
-->
<link rel="import" href="../bower_components/polymer/polymer.html">
<dom-module id="ray-view404">
<template>
<style>
:host {
display: block;
padding: 10px 20px;
}
</style>
<!--
If deploying in a folder replace href="/" with the full path to your site.
Such as: href=="http://polymerelements.github.io/polymer-starter-kit"
-->
Oops you hit a 404. <a href="/">Head back to home.</a>
</template>
<script>
Polymer({
is: 'ray-view404',
});
</script>
</dom-module>
+45
View File
@@ -0,0 +1,45 @@
<!--
@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
-->
<link rel="import" href="../bower_components/polymer/polymer.html">
<!-- shared styles for all views -->
<dom-module id="shared-styles">
<template>
<style>
.card {
margin: 24px;
padding: 16px;
color: #757575;
border-radius: 5px;
background-color: #fff;
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2);
}
.circle {
display: inline-block;
width: 64px;
height: 64px;
text-align: center;
color: #555;
border-radius: 50%;
background: #ddd;
font-size: 30px;
line-height: 64px;
}
h1 {
margin: 16px 0;
color: #212121;
font-size: 22px;
}
</style>
</template>
</dom-module>
+20
View File
@@ -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',
};
-68
View File
@@ -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
}
}
}
+10
View File
@@ -0,0 +1,10 @@
{
"env": {
"mocha": true
},
"globals": {
"assert": false,
"fixture": false,
"WCT": false
}
}
+29
View File
@@ -0,0 +1,29 @@
<!--
@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
-->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">
<title>Tests</title>
<script src="../bower_components/web-component-tester/browser.js"></script>
</head>
<body>
<script>
WCT.loadSuites([
'my-view1.html?dom=shady',
'my-view1.html?dom=shadow',
]);
</script>
</body>
</html>
+47
View File
@@ -0,0 +1,47 @@
<!--
@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
-->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">
<title>my-view1</title>
<script src="../bower_components/webcomponentsjs/webcomponents-lite.js"></script>
<script src="../bower_components/web-component-tester/browser.js"></script>
<!-- Import the element to test -->
<link rel="import" href="../src/my-view1.html">
</head>
<body>
<test-fixture id="basic">
<template>
<my-view1></my-view1>
</template>
</test-fixture>
<script>
suite('my-view1 tests', function() {
var home;
setup(function() {
home = fixture('basic');
});
test('Number in circle should be 1', function() {
var circle = Polymer.dom(home.root).querySelector('.circle');
assert.equal(circle.textContent, '1');
});
});
</script>
</body>
</html>
-19
View File
@@ -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;