Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Web Interface | Basic example working. #148

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 139 additions & 0 deletions cace/cace_web.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
from flask import Flask, render_template, request, Response
from .parameter import ParameterManager

import queue
import json
import os

# TODO: These should be options on the web interface
parameter_manager = ParameterManager(max_runs=None, run_path=None, jobs=None)
parameter_manager.find_datasheet(os.getcwd())

any_queue = queue.Queue()

app = Flask(__name__, template_folder='web', static_folder='web/static')


@app.route('/')
def homepage():
pnames = parameter_manager.get_all_pnames()
data = [{'name': pname} for pname in pnames]

return render_template(template_name_or_list='index.html', data=data)


@app.route('/runsim', methods=['POST'])
def runsim():
parameter_manager.results = {}
parameter_manager.result_types = {}

params = request.form.getlist('selected_params')

for param in params:
parameter_manager.queue_parameter(
param,
start_cb=lambda param, steps: (
any_queue.put(
{'task': 'start', 'param': param, 'steps': steps}
)
),
step_cb=lambda param: (
any_queue.put({'task': 'step', 'param': param})
),
cancel_cb=lambda param: (
any_queue.put({'task': 'cancel', 'param': param})
),
end_cb=lambda param: any_queue.put(
{'task': 'end', 'param': param}
),
)

# TODO: These should be options on the web interface
parameter_manager.set_runtime_options('force', False)
parameter_manager.set_runtime_options('noplot', False)
parameter_manager.set_runtime_options('nosim', False)
parameter_manager.set_runtime_options('sequential', False)
parameter_manager.set_runtime_options('netlist_source', 'best')
parameter_manager.set_runtime_options('parallel_parameters', 4)

parameter_manager.run_parameters_async()
return render_template(template_name_or_list='runsim.html', params=params)


def generate_sse():
num_params = parameter_manager.num_parameters()
datasheet = parameter_manager.datasheet['parameters']

params_completed = 0
while num_params != params_completed:
aqg = any_queue.get()

if aqg['task'] == 'end':
params_completed += 1
elif aqg['task'] == 'end_stream':
return

aqg['param'] = list(datasheet.keys())[
list(datasheet.values()).index(aqg['param'])
]
yield f'data: {json.dumps(aqg)}\n\n'

data = {'task': 'close'}
yield f'data: {json.dumps(data)}\n\n'


@app.route('/stream')
def stream():
return Response(generate_sse(), content_type='text/event-stream')


@app.route('/end_stream', methods=['POST'])
def end_stream():
any_queue.put({'task': 'end_stream'})
return '', 200


@app.route('/simresults')
def simresults():
parameter_manager.join_parameters()
result = []

summary_lines = parameter_manager.summarize_datasheet().split('\n')[7:-2]
lengths = {
param: len(
list(
parameter_manager.datasheet['parameters'][param]['spec'].keys()
)
)
for param in parameter_manager.get_all_pnames()
}
for param in parameter_manager.get_result_types().keys():
total = 0
for i in parameter_manager.get_all_pnames():
if i == param:
for j in range(lengths[param]):
row = summary_lines[total + j].split('|')
result.append(
{
'parameter_str': row[1],
'tool_str': row[2],
'result_str': row[3],
'min_limit_str': row[4],
'min_value_str': row[5],
'max_limit_str': row[6],
'max_value_str': row[7],
'typ_limit_str': row[8],
'typ_value_str': row[9],
'status_str': row[10],
}
)

total += lengths[i]

return render_template(
template_name_or_list='simresults.html', data=result
)


def web():
app.run(debug=True)
35 changes: 35 additions & 0 deletions cace/web/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="static/style.css" />
</head>
<body>
<form method="POST" action="/runsim">
<table>
<thead>
<tr>
<th>Select</th>
<th>Name</th>
</tr>
</thead>
<tbody>
{% for row in data %}
<tr>
<td class="checkbox-column">
<input
type="checkbox"
name="selected_params"
value="{{ row.name }}"
/>
</td>
<td>{{ row.name }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<button type="submit">Submit</button>
</form>
</body>
</html>
67 changes: 67 additions & 0 deletions cace/web/runsim.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="static/style.css" />
</head>
<body>
<table>
<thead>
<tr>
<th>Select</th>
<th>Name</th>
</tr>
</thead>
<tbody>
{% for param in params %}
<tr>
<td>{{ param }}</td>
<td>
<progress id="{{param}}" value="0" max="100"></progress>
</td>
</tr>
{% endfor %}
</tbody>
</table>

<button onclick="window.open('http://localhost:5000/simresults','_blank')">Simulation Results</button>
<p>Click the button above after all simulations have finished</p>

<script>
// Connect to the SSE stream
const eventSource = new EventSource("/stream");

// Listen for messages from the server
eventSource.onmessage = function (event) {
let data = JSON.parse(event.data);
let task = data.task;
if (task == "close") {
eventSource.close();
} else if (task == "start") {
console.log("start");
let outputDiv = document.getElementById(data.param);
outputDiv.max = data.steps;
} else if (task == "step") {
console.log("step");
let outputDiv = document.getElementById(data.param);
outputDiv.value++;
} else if (task == "cancel") {
} else if (task == "end") {
}
};

window.onbeforeunload = function () {
// Send a POST request to the /ping endpoint and ignore the response
fetch(`${window.location.origin}/end_stream`, {
method: "POST",
});
eventSource.close();
};

eventSource.onerror = function () {
console.error("Error occurred while receiving SSE.");
};
</script>
</body>
</html>
42 changes: 42 additions & 0 deletions cace/web/simresults.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="static/style.css" />
</head>
<body>
<table>
<thead>
<tr>
<th>Parameter</th>
<th>Tool</th>
<th>Result</th>
<th>Minimum Limit</th>
<th>Minimum Value</th>
<th>Typical Limit</th>
<th>Typical Value</th>
<th>Maximum Limit</th>
<th>Maximum Value</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{% for row in data %}
<tr>
<td>{{ row.parameter_str }}</td>
<td>{{ row.tool_str }}</td>
<td>{{ row.result_str }}</td>
<td>{{ row.min_limit_str }}</td>
<td>{{ row.min_value_str }}</td>
<td>{{ row.max_limit_str }}</td>
<td>{{ row.max_value_str }}</td>
<td>{{ row.typ_limit_str }}</td>
<td>{{ row.typ_value_str }}</td>
<td>{{ row.status_str }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</body>
</html>
17 changes: 17 additions & 0 deletions cace/web/static/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
table {
border-collapse: collapse;
margin: 25px 0;
font-size: 18px;
text-align: left;
}
table th,
table td {
padding: 8px;
border: 1px solid #ddd;
}
table th {
background-color: #f2f2f2;
}
progress {
width: 300px;
}