Remove old UI code. (#688)
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"extends": ["eslint:recommended", "google"],
|
||||
"env": {
|
||||
"browser": true
|
||||
},
|
||||
"plugins": [
|
||||
"html"
|
||||
],
|
||||
"rules": {
|
||||
"no-var": "off",
|
||||
"new-cap": ["error", { "capIsNewExceptions": ["Polymer"] }]
|
||||
},
|
||||
"globals": {
|
||||
"Polymer": true
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
* text=auto
|
||||
@@ -1,33 +0,0 @@
|
||||
<!-- 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
|
||||
@@ -1,3 +0,0 @@
|
||||
bower_components/
|
||||
build/
|
||||
node_modules/
|
||||
@@ -1,19 +0,0 @@
|
||||
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
|
||||
@@ -1,85 +0,0 @@
|
||||
# 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 [D3](https://d3js.org/) 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
|
||||
|
||||
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).
|
||||
@@ -1,460 +0,0 @@
|
||||
import aioredis
|
||||
import argparse
|
||||
import asyncio
|
||||
import binascii
|
||||
import collections
|
||||
import datetime
|
||||
import json
|
||||
import numpy as np
|
||||
import time
|
||||
import websockets
|
||||
|
||||
# Import flatbuffer bindings.
|
||||
from ray.core.generated.LocalSchedulerInfoMessage import \
|
||||
LocalSchedulerInfoMessage
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description="parse information 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
|
||||
|
||||
# This prefix must match the value defined in ray_redis_module.cc.
|
||||
DB_CLIENT_PREFIX = b"CL:"
|
||||
|
||||
|
||||
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 timestamp_to_date_string(timestamp):
|
||||
"""Convert a time stamp returned by time.time() to a formatted string."""
|
||||
return (datetime.datetime.fromtimestamp(timestamp)
|
||||
.strftime("%Y/%m/%d %H:%M:%S"))
|
||||
|
||||
|
||||
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 hgetall_as_dict(redis_conn, key):
|
||||
fields = await redis_conn.execute("hgetall", key)
|
||||
return {fields[2 * i]: fields[2 * i + 1] for i in range(len(fields) // 2)}
|
||||
|
||||
|
||||
# Cache information about the local schedulers.
|
||||
local_schedulers = {}
|
||||
errors = []
|
||||
|
||||
|
||||
def duration_to_string(duration):
|
||||
"""Format a duration in seconds as a string.
|
||||
|
||||
Args:
|
||||
duration (float): The duration in seconds.
|
||||
|
||||
Return:
|
||||
A more human-readable version of the string (for example, "3.5 hours" or
|
||||
"93 milliseconds").
|
||||
"""
|
||||
if duration > 3600 * 24:
|
||||
duration_str = "{0:0.1f} days".format(duration / (3600 * 24))
|
||||
elif duration > 3600:
|
||||
duration_str = "{0:0.1f} hours".format(duration / 3600)
|
||||
elif duration > 60:
|
||||
duration_str = "{0:0.1f} minutes".format(duration / 60)
|
||||
elif duration > 1:
|
||||
duration_str = "{0:0.1f} seconds".format(duration)
|
||||
elif duration > 0.001:
|
||||
duration_str = "{0:0.1f} milliseconds".format(duration * 1000)
|
||||
else:
|
||||
duration_str = "{} microseconds".format(int(duration * 1000000))
|
||||
return duration_str
|
||||
|
||||
|
||||
async def handle_get_statistics(websocket, redis_conn):
|
||||
cluster_start_time = float(await redis_conn.execute("get",
|
||||
"redis_start_time"))
|
||||
start_date = timestamp_to_date_string(cluster_start_time)
|
||||
|
||||
uptime = duration_to_string(time.time() - cluster_start_time)
|
||||
|
||||
client_keys = await redis_conn.execute("keys", "CL:*")
|
||||
clients = []
|
||||
for client_key in client_keys:
|
||||
client_fields = await hgetall_as_dict(redis_conn, client_key)
|
||||
clients.append(client_fields)
|
||||
ip_addresses = list(set([client[b"node_ip_address"].decode("ascii")
|
||||
for client in clients
|
||||
if client[b"client_type"] == b"local_scheduler"]))
|
||||
num_nodes = len(ip_addresses)
|
||||
reply = {"uptime": uptime,
|
||||
"start_date": start_date,
|
||||
"nodes": num_nodes,
|
||||
"addresses": ip_addresses}
|
||||
await websocket.send(json.dumps(reply))
|
||||
|
||||
|
||||
async def handle_get_drivers(websocket, redis_conn):
|
||||
keys = await redis_conn.execute("keys", "Drivers:*")
|
||||
drivers = []
|
||||
for key in keys:
|
||||
driver_fields = await hgetall_as_dict(redis_conn, key)
|
||||
driver_info = {
|
||||
"node ip address": driver_fields[b"node_ip_address"].decode("ascii"),
|
||||
"name": driver_fields[b"name"].decode("ascii")}
|
||||
|
||||
driver_info["start time"] = timestamp_to_date_string(
|
||||
float(driver_fields[b"start_time"]))
|
||||
|
||||
if b"end_time" in driver_fields:
|
||||
duration = (float(driver_fields[b"end_time"]) -
|
||||
float(driver_fields[b"start_time"]))
|
||||
else:
|
||||
duration = time.time() - float(driver_fields[b"start_time"])
|
||||
driver_info["duration"] = duration_to_string(duration)
|
||||
|
||||
if b"exception" in driver_fields:
|
||||
driver_info["status"] = "FAILED"
|
||||
elif b"end_time" not in driver_fields:
|
||||
driver_info["status"] = "IN PROGRESS"
|
||||
else:
|
||||
driver_info["status"] = "SUCCESS"
|
||||
|
||||
if b"exception" in driver_fields:
|
||||
driver_info["exception"] = driver_fields[b"exception"].decode("ascii")
|
||||
|
||||
drivers.append(driver_info)
|
||||
# Sort the drivers by their start times.
|
||||
reply = sorted(drivers, key=(lambda driver: driver["start time"]))[::-1]
|
||||
await websocket.send(json.dumps(reply))
|
||||
|
||||
|
||||
async def listen_for_errors(redis_ip_address, redis_port):
|
||||
pubsub_conn = await aioredis.create_connection(
|
||||
(redis_ip_address, redis_port), loop=loop)
|
||||
data_conn = await aioredis.create_connection((redis_ip_address, redis_port),
|
||||
loop=loop)
|
||||
|
||||
error_pattern = "__keyspace@0__:ErrorKeys"
|
||||
await pubsub_conn.execute_pubsub("psubscribe", error_pattern)
|
||||
channel = pubsub_conn.pubsub_patterns[error_pattern]
|
||||
print("Listening for error messages...")
|
||||
index = 0
|
||||
while (await channel.wait_message()):
|
||||
await channel.get()
|
||||
info = await data_conn.execute("lrange", "ErrorKeys", index, -1)
|
||||
|
||||
for error_key in info:
|
||||
worker, task = key_to_hex_identifiers(error_key)
|
||||
# TODO(richard): Filter out workers so that only relevant task errors are
|
||||
# necessary.
|
||||
result = await data_conn.execute("hget", error_key, "message")
|
||||
result = result.decode("ascii")
|
||||
# TODO(richard): Maybe also get rid of the coloring.
|
||||
errors.append({"driver_id": worker,
|
||||
"task_id": task,
|
||||
"error": result})
|
||||
index += 1
|
||||
|
||||
|
||||
async def handle_get_errors(websocket):
|
||||
"""Send error messages to the frontend."""
|
||||
await websocket.send(json.dumps(errors))
|
||||
|
||||
node_info = collections.OrderedDict()
|
||||
worker_info = collections.OrderedDict()
|
||||
|
||||
|
||||
async def handle_get_recent_tasks(websocket, redis_conn, num_tasks):
|
||||
# First update the cache of worker information.
|
||||
worker_keys = await redis_conn.execute("keys", "Workers:*")
|
||||
for key in worker_keys:
|
||||
worker_id = hex_identifier(key[len("Workers:"):])
|
||||
if worker_id not in worker_info:
|
||||
worker_info[worker_id] = await hgetall_as_dict(redis_conn, key)
|
||||
node_ip_address = (worker_info[worker_id][b"node_ip_address"]
|
||||
.decode("ascii"))
|
||||
if node_ip_address not in node_info:
|
||||
node_info[node_ip_address] = {"workers": []}
|
||||
node_info[node_ip_address]["workers"].append(worker_id)
|
||||
|
||||
keys = await redis_conn.execute("keys", "event_log:*")
|
||||
if len(keys) == 0:
|
||||
# There are no tasks, so send a message to the client saying so.
|
||||
await websocket.send(json.dumps({"num_tasks": 0}))
|
||||
else:
|
||||
timestamps = []
|
||||
contents = []
|
||||
for key in keys:
|
||||
content = await redis_conn.execute("lrange", key, "0", "-1")
|
||||
contents.append(json.loads(content[0].decode()))
|
||||
timestamps += [timestamp for (timestamp, task, kind, info)
|
||||
in contents[-1] if task == "ray:task"]
|
||||
|
||||
timestamps.sort()
|
||||
time_cutoff = timestamps[(-2 * num_tasks):][0]
|
||||
|
||||
max_time = timestamps[-1]
|
||||
min_time = time_cutoff - (max_time - time_cutoff) * 0.1
|
||||
max_time = max_time + (max_time - time_cutoff) * 0.1
|
||||
|
||||
worker_ids = list(worker_info.keys())
|
||||
node_ip_addresses = list(node_info.keys())
|
||||
|
||||
num_tasks = 0
|
||||
task_data = [{"task_data": [],
|
||||
"num_workers": len(node_info[node_ip_address]["workers"])}
|
||||
for node_ip_address in node_ip_addresses]
|
||||
for i in range(len(keys)):
|
||||
worker_id, task_id = key_to_hex_identifiers(keys[i])
|
||||
data = contents[i]
|
||||
if worker_id not in worker_ids:
|
||||
# This case should be extremely rare.
|
||||
raise Exception("A worker ID was not present in the list of worker "
|
||||
"IDs.")
|
||||
node_ip_address = (worker_info[worker_id][b"node_ip_address"]
|
||||
.decode("ascii"))
|
||||
worker_index = node_info[node_ip_address]["workers"].index(worker_id)
|
||||
node_index = node_ip_addresses.index(node_ip_address)
|
||||
|
||||
task_times = [timestamp for (timestamp, task, kind, info) in data
|
||||
if task == "ray:task"]
|
||||
if task_times[1] <= time_cutoff:
|
||||
continue
|
||||
|
||||
task_get_arguments_times = [timestamp for (timestamp, task, kind, info)
|
||||
in data if task == "ray:task:get_arguments"]
|
||||
task_execute_times = [timestamp for (timestamp, task, kind, info)
|
||||
in data if task == "ray:task:execute"]
|
||||
task_store_outputs_times = [timestamp for (timestamp, task, kind, info)
|
||||
in data if task == "ray:task:store_outputs"]
|
||||
task_info = {
|
||||
"task": task_times,
|
||||
"get_arguments": task_get_arguments_times,
|
||||
"execute": task_execute_times,
|
||||
"store_outputs": task_store_outputs_times,
|
||||
"worker_index": worker_index,
|
||||
"node_ip_address": node_ip_address,
|
||||
"task_formatted_time": duration_to_string(task_times[1] -
|
||||
task_times[0]),
|
||||
"get_arguments_formatted_time":
|
||||
duration_to_string(task_get_arguments_times[1] -
|
||||
task_get_arguments_times[0])}
|
||||
if len(task_execute_times) == 2:
|
||||
task_info["execute_formatted_time"] = duration_to_string(
|
||||
task_execute_times[1] - task_execute_times[0])
|
||||
if len(task_store_outputs_times) == 2:
|
||||
task_info["store_outputs_formatted_time"] = duration_to_string(
|
||||
task_store_outputs_times[1] - task_store_outputs_times[0])
|
||||
task_data[node_index]["task_data"].append(task_info)
|
||||
num_tasks += 1
|
||||
reply = {"min_time": min_time,
|
||||
"max_time": max_time,
|
||||
"num_tasks": num_tasks,
|
||||
"task_data": task_data}
|
||||
await websocket.send(json.dumps(reply))
|
||||
|
||||
|
||||
async def send_heartbeat_payload(websocket):
|
||||
"""Send heartbeat updates to the frontend every half second."""
|
||||
while True:
|
||||
reply = []
|
||||
for local_scheduler_id, local_scheduler in local_schedulers.items():
|
||||
current_time = time.time()
|
||||
local_scheduler_info = {
|
||||
"local scheduler ID": local_scheduler_id,
|
||||
"time since heartbeat":
|
||||
(duration_to_string(current_time -
|
||||
local_scheduler["last_heartbeat"])),
|
||||
"time since heartbeat numeric":
|
||||
str(current_time - local_scheduler["last_heartbeat"]),
|
||||
"node ip address": local_scheduler["node_ip_address"]}
|
||||
reply.append(local_scheduler_info)
|
||||
# Send the payload to the frontend.
|
||||
await websocket.send(json.dumps(reply))
|
||||
# Wait for a little while so as not to overwhelm the frontend.
|
||||
await asyncio.sleep(0.5)
|
||||
|
||||
|
||||
async def send_heartbeats(websocket, redis_conn):
|
||||
# First update the local scheduler info locally.
|
||||
client_keys = await redis_conn.execute("keys", "CL:*")
|
||||
for client_key in client_keys:
|
||||
client_fields = await hgetall_as_dict(redis_conn, client_key)
|
||||
if client_fields[b"client_type"] == b"local_scheduler":
|
||||
local_scheduler_id = hex_identifier(client_fields[b"ray_client_id"])
|
||||
local_schedulers[local_scheduler_id] = {
|
||||
"node_ip_address": client_fields[b"node_ip_address"].decode("ascii"),
|
||||
"local_scheduler_socket_name":
|
||||
client_fields[b"local_scheduler_socket_name"].decode("ascii"),
|
||||
"aux_address": client_fields[b"aux_address"].decode("ascii"),
|
||||
"last_heartbeat": -1 * np.inf}
|
||||
|
||||
# Subscribe to local scheduler heartbeats.
|
||||
await redis_conn.execute_pubsub("subscribe", "local_schedulers")
|
||||
|
||||
# Start a method in the background to periodically update the frontend.
|
||||
asyncio.ensure_future(send_heartbeat_payload(websocket))
|
||||
|
||||
while True:
|
||||
msg = await redis_conn.pubsub_channels["local_schedulers"].get()
|
||||
heartbeat = LocalSchedulerInfoMessage.GetRootAsLocalSchedulerInfoMessage(
|
||||
msg, 0)
|
||||
local_scheduler_id_bytes = heartbeat.DbClientId()
|
||||
local_scheduler_id = hex_identifier(local_scheduler_id_bytes)
|
||||
if local_scheduler_id not in local_schedulers:
|
||||
# A new local scheduler has joined the cluster. Ignore it. This won't be
|
||||
# displayed in the UI until the page is refreshed.
|
||||
continue
|
||||
local_schedulers[local_scheduler_id]["last_heartbeat"] = time.time()
|
||||
|
||||
|
||||
async def cache_data_from_redis(redis_ip_address, redis_port):
|
||||
"""Open up ports to listen for new updates from Redis."""
|
||||
# TODO(richard): A lot of code needs to be ported in order to open new
|
||||
# websockets.
|
||||
|
||||
asyncio.ensure_future(listen_for_errors(redis_ip_address, redis_port))
|
||||
|
||||
|
||||
async def handle_get_log_files(websocket, redis_conn):
|
||||
reply = {}
|
||||
# First get all keys for the log file lists.
|
||||
log_file_list_keys = await redis_conn.execute("keys", "LOG_FILENAMES:*")
|
||||
for log_file_list_key in log_file_list_keys:
|
||||
node_ip_address = log_file_list_key.decode("ascii").split(":")[1]
|
||||
reply[node_ip_address] = {}
|
||||
# Get all of the log filenames for this node IP address.
|
||||
log_filenames = await redis_conn.execute("lrange", log_file_list_key, 0,
|
||||
-1)
|
||||
for log_filename in log_filenames:
|
||||
log_filename_key = "LOGFILE:{}:{}".format(node_ip_address,
|
||||
log_filename.decode("ascii"))
|
||||
logfile = await redis_conn.execute("lrange", log_filename_key, 0, -1)
|
||||
logfile = [line.decode("ascii") for line in logfile]
|
||||
reply[node_ip_address][log_filename.decode("ascii")] = logfile
|
||||
|
||||
# Send the reply back to the front end.
|
||||
await websocket.send(json.dumps(reply))
|
||||
|
||||
|
||||
async def serve_requests(websocket, path):
|
||||
redis_conn = await aioredis.create_connection((redis_ip_address, redis_port),
|
||||
loop=loop)
|
||||
while True:
|
||||
command = json.loads(await websocket.recv())
|
||||
print("received command {}".format(command))
|
||||
|
||||
if command["command"] == "get-statistics":
|
||||
await handle_get_statistics(websocket, redis_conn)
|
||||
elif command["command"] == "get-drivers":
|
||||
await handle_get_drivers(websocket, redis_conn)
|
||||
elif command["command"] == "get-recent-tasks":
|
||||
await handle_get_recent_tasks(websocket, redis_conn, command["num"])
|
||||
elif command["command"] == "get-errors":
|
||||
await handle_get_errors(websocket)
|
||||
elif command["command"] == "get-heartbeats":
|
||||
await send_heartbeats(websocket, redis_conn)
|
||||
elif command["command"] == "get-log-files":
|
||||
await handle_get_log_files(websocket, redis_conn)
|
||||
|
||||
if command["command"] == "get-workers":
|
||||
result = []
|
||||
workers = await redis_conn.execute("keys", "WorkerInfo:*")
|
||||
for key in workers:
|
||||
content = await redis_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 redis_conn.execute("keys", "CL:*")
|
||||
for key in clients:
|
||||
content = await redis_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 redis_conn.execute("keys", "OI:*")
|
||||
for key in objects:
|
||||
content = await redis_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 redis_conn.execute("keys", "TT:*")
|
||||
for key in tasks:
|
||||
content = await redis_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 = collections.defaultdict(list)
|
||||
for key in await redis_conn.execute("keys", "event_log:*"):
|
||||
worker_id, task_id = key_to_hex_identifiers(key)
|
||||
content = await redis_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 redis_conn.execute("keys", "event_log:*"):
|
||||
worker_id, task_id = key_to_hex_identifiers(key)
|
||||
answer = await redis_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_ip_address, redis_port = redis_address[0], int(redis_address[1])
|
||||
|
||||
# The port here must match the value used by the frontend to connect over
|
||||
# websockets. TODO(richard): Automatically increment the port if it is
|
||||
# already taken.
|
||||
port = 8888
|
||||
|
||||
loop.run_until_complete(cache_data_from_redis(redis_ip_address, redis_port))
|
||||
|
||||
start_server = websockets.serve(serve_requests, "localhost", port)
|
||||
loop.run_until_complete(start_server)
|
||||
loop.run_forever()
|
||||
@@ -1,26 +0,0 @@
|
||||
{
|
||||
"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
|
||||
}
|
||||
|
Before Width: | Height: | Size: 5.3 KiB |
|
Before Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 3.2 KiB |
@@ -1,107 +0,0 @@
|
||||
<!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">
|
||||
|
||||
<script src="https://d3js.org/d3.v4.min.js"></script>
|
||||
|
||||
<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>
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"name": "polymer-starter-kit",
|
||||
"license": "BSD-3-Clause",
|
||||
"devDependencies": {
|
||||
"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"
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
]
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
/**
|
||||
* @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.'
|
||||
);
|
||||
@@ -1,140 +0,0 @@
|
||||
<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="cluster-health" href="/cluster-health">Cluster Health</a>
|
||||
<a name="objects" href="/objects">Objects</a>
|
||||
<a name="tasks" href="/tasks">Tasks</a>
|
||||
<a name="events" href="/events">Events</a>
|
||||
<a name="errors" href="/errors">Errors</a>
|
||||
<a name="timeline" href="/timeline">Timeline</a>
|
||||
<a name="recent-tasks" href="/recent-tasks">Recent Tasks</a>
|
||||
<a name="log-files" href="/log-files">Log Files</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-cluster-health name="cluster-health"></ray-cluster-health>
|
||||
<ray-objects name="objects"></ray-objects>
|
||||
<ray-tasks name="tasks"></ray-tasks>
|
||||
<ray-events name="events"></ray-events>
|
||||
<ray-errors name="errors"></ray-errors>
|
||||
<ray-timeline name="timeline"></ray-timeline>
|
||||
<ray-recent-tasks name="recent-tasks"></ray-recent-tasks>
|
||||
<ray-log-files name="log-files"></ray-log-files>
|
||||
<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>
|
||||
@@ -1,54 +0,0 @@
|
||||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
<link rel="import" href="shared-styles.html">
|
||||
|
||||
<dom-module id="ray-cluster-health">
|
||||
<template>
|
||||
<style include="shared-styles">
|
||||
:host {
|
||||
display: block;
|
||||
|
||||
padding: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="card">
|
||||
<h1>Ray Cluster Health</h1>
|
||||
<h2>Clients</h2>
|
||||
<vaadin-grid id="local-schedulers">
|
||||
<table>
|
||||
<colgroup>
|
||||
<col name="local scheduler ID"/>
|
||||
<col name="node ip address"/>
|
||||
<col name="time since heartbeat"/>
|
||||
</colgroup>
|
||||
</table>
|
||||
</vaadin-grid>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
var backend_address = "ws://127.0.0.1:8888";
|
||||
|
||||
Polymer({
|
||||
is: 'ray-cluster-health',
|
||||
ready: function() {
|
||||
|
||||
var self = this;
|
||||
var socket = new WebSocket(backend_address);
|
||||
|
||||
var local_schedulers = Polymer.dom(this.root).querySelector("#local-schedulers");
|
||||
socket.onopen = function() {
|
||||
socket.send(JSON.stringify({"command": "get-heartbeats"}));
|
||||
}
|
||||
|
||||
socket.onmessage = function(messageEvent) {
|
||||
var heartbeat_info = JSON.parse(messageEvent.data);
|
||||
local_schedulers.items = heartbeat_info;
|
||||
// TODO(rkn): Figure out how to make the rows that correspond to dead
|
||||
// nodes appear red.
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</dom-module>
|
||||
@@ -1,46 +0,0 @@
|
||||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
<link rel="import" href="shared-styles.html">
|
||||
|
||||
<dom-module id="ray-errors">
|
||||
<template>
|
||||
<style include="shared-styles">
|
||||
:host {
|
||||
display: block;
|
||||
|
||||
padding: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="card">
|
||||
<h1>Errors</h1>
|
||||
<vaadin-grid id="errors">
|
||||
<table>
|
||||
<colgroup>
|
||||
<col name="driver_id" sortable="" sort-direction="desc"/>
|
||||
<col name="task_id" sortable="" sort-direction="desc"/>
|
||||
<col name="error" sortable="" sort-direction="desc"/>
|
||||
</colgroup>
|
||||
</table>
|
||||
</vaadin-grid>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
var backend_address = "ws://127.0.0.1:8888";
|
||||
Polymer({
|
||||
is: 'ray-errors',
|
||||
ready: function() {
|
||||
var eventSocket = new WebSocket(backend_address);
|
||||
var errors = Polymer.dom(this.root).querySelector("#errors");
|
||||
|
||||
eventSocket.onopen = function() {
|
||||
eventSocket.send(JSON.stringify({"command": "get-errors"}));
|
||||
}
|
||||
eventSocket.onmessage = function(answer) {
|
||||
console.dir(answer.data);
|
||||
errors.items = JSON.parse(answer.data);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</dom-module>
|
||||
@@ -1,47 +0,0 @@
|
||||
<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>
|
||||
@@ -1,21 +0,0 @@
|
||||
<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>
|
||||
@@ -1,90 +0,0 @@
|
||||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
<link rel="import" href="shared-styles.html">
|
||||
|
||||
<dom-module id="ray-log-files">
|
||||
<template>
|
||||
<style include="shared-styles">
|
||||
:host {
|
||||
display: block;
|
||||
|
||||
padding: 10px;
|
||||
}
|
||||
rect:hover
|
||||
{
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<div class="card">
|
||||
<h1>Ray Log Files</h1>
|
||||
<div id="all_log_files"></div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
var backend_address = "ws://127.0.0.1:8888";
|
||||
|
||||
Polymer({
|
||||
is: 'ray-log-files',
|
||||
ready: function() {
|
||||
|
||||
var self = this;
|
||||
var socket = new WebSocket(backend_address);
|
||||
|
||||
socket.onopen = function() {
|
||||
socket.send(JSON.stringify({"command": "get-log-files"}));
|
||||
}
|
||||
|
||||
socket.onmessage = function(messageEvent) {
|
||||
var reply = JSON.parse(messageEvent.data);
|
||||
console.log(reply);
|
||||
for (i = 0; i < Object.keys(reply).length; i++) {
|
||||
key = Object.keys(reply)[i];
|
||||
dict = reply[key];
|
||||
|
||||
var node = document.createElement("LI");
|
||||
var bold_node = document.createElement("B");
|
||||
var textnode = document.createTextNode(key);
|
||||
bold_node.appendChild(textnode);
|
||||
node.appendChild(bold_node);
|
||||
|
||||
var node_log_files_node = document.createElement("UL");
|
||||
|
||||
for (j = 0; j < Object.keys(dict).length; j++) {
|
||||
file_key = Object.keys(dict)[j];
|
||||
line_list = dict[file_key];
|
||||
console.log(file_key);
|
||||
console.log(line_list);
|
||||
|
||||
var logfile_node = document.createElement("LI");
|
||||
var log_filename_node = document.createTextNode(file_key);
|
||||
var bold_node = document.createElement("B");
|
||||
bold_node.appendChild(log_filename_node);
|
||||
logfile_node.appendChild(bold_node);
|
||||
|
||||
for (k = 0; k < line_list.length; k++) {
|
||||
var paragraph = document.createElement("BR");
|
||||
var logfile_line_node = document.createTextNode(line_list[k]);
|
||||
logfile_node.append(paragraph);
|
||||
logfile_node.append(logfile_line_node);
|
||||
}
|
||||
node_log_files_node.append(logfile_node);
|
||||
|
||||
}
|
||||
node.appendChild(node_log_files_node);
|
||||
|
||||
self.$.all_log_files.appendChild(node);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
socket.onclose = function(closeEvent) {
|
||||
console.log(closeEvent)
|
||||
}
|
||||
},
|
||||
|
||||
});
|
||||
</script>
|
||||
</dom-module>
|
||||
@@ -1,57 +0,0 @@
|
||||
<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>
|
||||
@@ -1,138 +0,0 @@
|
||||
<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>Cluster Statistics</h2>
|
||||
<div id="uptime"></div>
|
||||
<div id="nodes"></div>
|
||||
<div id="addresses"></div>
|
||||
|
||||
<h2>Drivers</h2>
|
||||
<vaadin-grid id="drivers">
|
||||
<table>
|
||||
<colgroup>
|
||||
<col name="name"/>
|
||||
<col name="start time"/>
|
||||
<col name="duration"/>
|
||||
<col name="status"/>
|
||||
<col name="node ip address"/>
|
||||
</colgroup>
|
||||
</table>
|
||||
</vaadin-grid>
|
||||
|
||||
<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 statisticsSocket = new WebSocket(backend_address);
|
||||
var uptime = Polymer.dom(this.root).querySelector("#uptime");
|
||||
var nodes = Polymer.dom(this.root).querySelector("#nodes");
|
||||
var addresses = Polymer.dom(this.root).querySelector("#addresses");
|
||||
statisticsSocket.onopen = function() {
|
||||
statisticsSocket.send(JSON.stringify({"command": "get-statistics"}))
|
||||
}
|
||||
statisticsSocket.onmessage = function(answer) {
|
||||
var statistics = JSON.parse(answer.data);
|
||||
uptime.innerHTML = "<b>Total Uptime:</b> " + statistics.uptime + " <b>since</b> " + statistics.start_date;
|
||||
nodes.innerHTML = "<b>Number of Machines:</b> " + statistics.nodes;
|
||||
addresses.innerHTML = "<b>IP addresses:</b> " + statistics.addresses;
|
||||
}
|
||||
|
||||
var driverSocket = new WebSocket(backend_address);
|
||||
var drivers = Polymer.dom(this.root).querySelector("#drivers");
|
||||
driverSocket.onopen = function() {
|
||||
driverSocket.send(JSON.stringify({"command": "get-drivers"}));
|
||||
}
|
||||
driverSocket.onmessage = function(answer) {
|
||||
drivers.items = JSON.parse(answer.data);
|
||||
drivers.visibleRows = drivers.items.length;
|
||||
|
||||
// Add a row details generator
|
||||
drivers.rowDetailsGenerator = function(rowIndex) {
|
||||
var elem = document.createElement("pre");
|
||||
elem.setAttribute("class", "userdetailswrapper");
|
||||
elem.style.whiteSpace = "pre-wrap";
|
||||
|
||||
drivers.getItem(rowIndex, function(error, item) {
|
||||
if (!error) {
|
||||
if (item.exception) {
|
||||
elem.innerHTML = item.exception + "\n\n";
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return elem;
|
||||
};
|
||||
|
||||
var detailsOpenIndex = -1;
|
||||
|
||||
// Show details for the selected row
|
||||
drivers.addEventListener("selected-items-changed", function() {
|
||||
drivers.setRowDetailsVisible(detailsOpenIndex, false);
|
||||
var selected = drivers.selection.selected();
|
||||
if (selected.length == 1) {
|
||||
drivers.setRowDetailsVisible(selected[0], true);
|
||||
detailsOpenIndex = selected[0];
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
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>
|
||||
@@ -1,71 +0,0 @@
|
||||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
<link rel="import" href="shared-styles.html">
|
||||
|
||||
<script src="recent-tasks.js"></script>
|
||||
|
||||
<dom-module id="ray-recent-tasks">
|
||||
<template>
|
||||
<style include="shared-styles">
|
||||
:host {
|
||||
display: block;
|
||||
|
||||
padding: 10px;
|
||||
}
|
||||
rect:hover
|
||||
{
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<div class="card">
|
||||
<h1>Ray Recent Tasks</h1>
|
||||
<input value=100 id="input_num_tasks"></input>
|
||||
<button id="refresh_button">Refresh</button>
|
||||
<br></br>
|
||||
|
||||
<div id="all_recent_tasks"></div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
var backend_address = "ws://127.0.0.1:8888";
|
||||
|
||||
Polymer({
|
||||
is: 'ray-recent-tasks',
|
||||
ready: function() {
|
||||
|
||||
var self = this;
|
||||
var socket = new WebSocket(backend_address);
|
||||
|
||||
socket.onopen = function() {
|
||||
socket.send(JSON.stringify({"command": "get-recent-tasks", "num": 100}));
|
||||
}
|
||||
|
||||
socket.onmessage = function(messageEvent) {
|
||||
var task_info = JSON.parse(messageEvent.data);
|
||||
console.log(task_info.num_tasks);
|
||||
if (task_info["num_tasks"] == 0) {
|
||||
console.log("No tasks yet.");
|
||||
return;
|
||||
}
|
||||
recent_tasks = new RecentTasks(self.$.all_recent_tasks);
|
||||
recent_tasks.draw_new_tasks(task_info, self.offsetWidth - 100);
|
||||
}
|
||||
|
||||
socket.onclose = function(closeEvent) {
|
||||
console.log(closeEvent)
|
||||
}
|
||||
|
||||
d3.select(this.$.refresh_button)
|
||||
.on("click", function () {
|
||||
recent_tasks.erase();
|
||||
var num_tasks_to_get = self.$.input_num_tasks.value;
|
||||
socket.send(JSON.stringify({"command": "get-recent-tasks", "num": parseInt(num_tasks_to_get)}));
|
||||
});
|
||||
},
|
||||
|
||||
});
|
||||
</script>
|
||||
</dom-module>
|
||||
@@ -1,45 +0,0 @@
|
||||
<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>
|
||||
@@ -1,71 +0,0 @@
|
||||
<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 backend_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(backend_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>
|
||||
@@ -1,35 +0,0 @@
|
||||
<!--
|
||||
@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>
|
||||
@@ -1,107 +0,0 @@
|
||||
RecentTasks = function(all_recent_tasks_elem) {
|
||||
var self = this;
|
||||
|
||||
var verticalPadding = 10;
|
||||
var barHeight = 25;
|
||||
|
||||
var all_recent_tasks_div = d3.select(all_recent_tasks_elem);
|
||||
|
||||
this.generate_task_info = function(d) {
|
||||
return "<div><b>Total Time:</b> " + d.task_formatted_time + "</div>" +
|
||||
"<div><b>Time Getting Arguments:</b> " + d.get_arguments_formatted_time + "</div>" +
|
||||
"<div><b>Time in Execution:</b> " + d.execute_formatted_time + "</div>" +
|
||||
"<div><b>Time Storing Outputs:</b> " + d.store_outputs_formatted_time + "</div>";
|
||||
}
|
||||
|
||||
this.draw_new_node_tasks = function(all_task_info, task_info, width, svg, info) {
|
||||
var height = task_info.num_workers * barHeight + 2 * verticalPadding;
|
||||
|
||||
svg.attr("width", width)
|
||||
.attr("height", height);
|
||||
|
||||
var borderPath = svg.append("rect")
|
||||
.attr("x", 0)
|
||||
.attr("y", 0)
|
||||
.attr("height", height)
|
||||
.attr("width", width)
|
||||
.style("stroke", "black")
|
||||
.style("fill", "none")
|
||||
.style("stroke-width", 1);
|
||||
|
||||
var x = d3.scaleLinear()
|
||||
.domain([all_task_info.min_time, all_task_info.max_time])
|
||||
.range([-1, width + 1]);
|
||||
|
||||
var task_rects = svg.append("g").attr("class", "task_rects");
|
||||
var get_arguments_rects = svg.append("g").attr("class", "get_arguments_rects");
|
||||
var execute_rects = svg.append("g").attr("class", "execute_rects");
|
||||
var store_outputs_rects = svg.append("g").attr("class", "store_outputs_rects");
|
||||
|
||||
task_rects.selectAll("rect")
|
||||
.data(task_info.task_data)
|
||||
.enter()
|
||||
.append("rect")
|
||||
.attr("x", function (d) { return x(d.task[0]); })
|
||||
.attr("y", function (d) { return verticalPadding + d.worker_index * barHeight; })
|
||||
.attr("width", function (d) { return x(d.task[1]) - x(d.task[0]); })
|
||||
.attr("height", function (d) { return barHeight - 1; })
|
||||
.attr("fill", "orange")
|
||||
.attr("id", function (d) { d.store_outputs[1]; })
|
||||
.on("click", function(d, i) {
|
||||
info.html(self.generate_task_info(d));
|
||||
})
|
||||
|
||||
get_arguments_rects.selectAll("rect")
|
||||
.data(task_info.task_data)
|
||||
.enter()
|
||||
.append("rect")
|
||||
.attr("x", function (d) { return x(d.get_arguments[0]); })
|
||||
.attr("y", function (d) { return verticalPadding + d.worker_index * barHeight + 1; })
|
||||
.attr("width", function (d) { return x(d.get_arguments[1]) - x(d.get_arguments[0]); })
|
||||
.attr("height", function (d) { return barHeight - 3; })
|
||||
.attr("fill", "black")
|
||||
.on("click", function(d, i) {
|
||||
info.html(self.generate_task_info(d));
|
||||
})
|
||||
|
||||
execute_rects.selectAll("rect")
|
||||
.data(task_info.task_data)
|
||||
.enter()
|
||||
.append("rect")
|
||||
.attr("x", function (d) { return x(d.execute[0]); })
|
||||
.attr("y", function (d) { return verticalPadding + d.worker_index * barHeight + 1; })
|
||||
.attr("width", function (d) { return x(d.execute[1]) - x(d.execute[0]); })
|
||||
.attr("height", function (d) { return barHeight - 3; })
|
||||
.attr("fill", "blue")
|
||||
.on("click", function(d, i) {
|
||||
info.html(self.generate_task_info(d));
|
||||
})
|
||||
|
||||
store_outputs_rects.selectAll("rect")
|
||||
.data(task_info.task_data)
|
||||
.enter()
|
||||
.append("rect")
|
||||
.attr("x", function (d) { return x(d.store_outputs[0]); })
|
||||
.attr("y", function (d) { return verticalPadding + d.worker_index * barHeight + 1; })
|
||||
.attr("width", function (d) { return x(d.store_outputs[1]) - x(d.store_outputs[0]); })
|
||||
.attr("height", function (d) { return barHeight - 3; })
|
||||
.attr("fill", "green")
|
||||
.on("click", function(d, i) {
|
||||
info.html(self.generate_task_info(d));
|
||||
})
|
||||
}
|
||||
|
||||
this.draw_new_tasks = function(all_task_info, width) {
|
||||
// Call draw_new_node_tasks once for each node.
|
||||
for (i = 0; i < all_task_info.task_data.length; i++) {
|
||||
var new_svg = all_recent_tasks_div.append("svg");
|
||||
var info = all_recent_tasks_div.append("div");
|
||||
this.draw_new_node_tasks(all_task_info, all_task_info.task_data[i], width, new_svg, info);
|
||||
}
|
||||
}
|
||||
|
||||
this.erase = function() {
|
||||
all_recent_tasks_div.selectAll("svg").remove();
|
||||
all_recent_tasks_div.selectAll("div").remove();
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
<!--
|
||||
@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>
|
||||
@@ -1,20 +0,0 @@
|
||||
/**
|
||||
* @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',
|
||||
};
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"env": {
|
||||
"mocha": true
|
||||
},
|
||||
"globals": {
|
||||
"assert": false,
|
||||
"fixture": false,
|
||||
"WCT": false
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
<!--
|
||||
@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>
|
||||
@@ -1,47 +0,0 @@
|
||||
<!--
|
||||
@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>
|
||||