ivoryos 1.2.2__py3-none-any.whl → 1.2.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of ivoryos might be problematic. Click here for more details.
- ivoryos/routes/auth/templates/login.html +25 -0
- ivoryos/routes/auth/templates/signup.html +32 -0
- ivoryos/routes/control/templates/controllers.html +166 -0
- ivoryos/routes/control/templates/controllers_new.html +112 -0
- ivoryos/routes/data/templates/components/step_card.html +13 -0
- ivoryos/routes/data/templates/workflow_database.html +109 -0
- ivoryos/routes/data/templates/workflow_view.html +130 -0
- ivoryos/routes/design/templates/components/action_form.html +53 -0
- ivoryos/routes/design/templates/components/actions_panel.html +25 -0
- ivoryos/routes/design/templates/components/autofill_toggle.html +10 -0
- ivoryos/routes/design/templates/components/canvas.html +5 -0
- ivoryos/routes/design/templates/components/canvas_footer.html +9 -0
- ivoryos/routes/design/templates/components/canvas_header.html +75 -0
- ivoryos/routes/design/templates/components/canvas_main.html +34 -0
- ivoryos/routes/design/templates/components/deck_selector.html +10 -0
- ivoryos/routes/design/templates/components/edit_action_form.html +38 -0
- ivoryos/routes/design/templates/components/instruments_panel.html +66 -0
- ivoryos/routes/design/templates/components/modals/drop_modal.html +17 -0
- ivoryos/routes/design/templates/components/modals/json_modal.html +22 -0
- ivoryos/routes/design/templates/components/modals/new_script_modal.html +17 -0
- ivoryos/routes/design/templates/components/modals/rename_modal.html +23 -0
- ivoryos/routes/design/templates/components/modals/saveas_modal.html +27 -0
- ivoryos/routes/design/templates/components/modals.html +6 -0
- ivoryos/routes/design/templates/components/python_code_overlay.html +39 -0
- ivoryos/routes/design/templates/components/sidebar.html +15 -0
- ivoryos/routes/design/templates/components/text_to_code_panel.html +20 -0
- ivoryos/routes/design/templates/experiment_builder.html +41 -0
- ivoryos/routes/execute/templates/components/error_modal.html +20 -0
- ivoryos/routes/execute/templates/components/logging_panel.html +31 -0
- ivoryos/routes/execute/templates/components/progress_panel.html +27 -0
- ivoryos/routes/execute/templates/components/run_panel.html +9 -0
- ivoryos/routes/execute/templates/components/run_tabs.html +17 -0
- ivoryos/routes/execute/templates/components/tab_bayesian.html +398 -0
- ivoryos/routes/execute/templates/components/tab_configuration.html +98 -0
- ivoryos/routes/execute/templates/components/tab_repeat.html +14 -0
- ivoryos/routes/execute/templates/experiment_run.html +294 -0
- ivoryos/routes/library/templates/library.html +92 -0
- ivoryos/routes/main/templates/help.html +141 -0
- ivoryos/routes/main/templates/home.html +103 -0
- ivoryos/static/favicon.ico +0 -0
- ivoryos/static/gui_annotation/Slide1.png +0 -0
- ivoryos/static/gui_annotation/Slide2.PNG +0 -0
- ivoryos/static/js/action_handlers.js +213 -0
- ivoryos/static/js/db_delete.js +23 -0
- ivoryos/static/js/overlay.js +12 -0
- ivoryos/static/js/script_metadata.js +39 -0
- ivoryos/static/js/socket_handler.js +125 -0
- ivoryos/static/js/sortable_card.js +24 -0
- ivoryos/static/js/sortable_design.js +138 -0
- ivoryos/static/js/ui_state.js +113 -0
- ivoryos/static/logo.webp +0 -0
- ivoryos/static/style.css +211 -0
- ivoryos/templates/base.html +157 -0
- ivoryos/version.py +1 -1
- {ivoryos-1.2.2.dist-info → ivoryos-1.2.3.dist-info}/METADATA +1 -1
- ivoryos-1.2.3.dist-info/RECORD +100 -0
- ivoryos-1.2.2.dist-info/RECORD +0 -47
- {ivoryos-1.2.2.dist-info → ivoryos-1.2.3.dist-info}/WHEEL +0 -0
- {ivoryos-1.2.2.dist-info → ivoryos-1.2.3.dist-info}/licenses/LICENSE +0 -0
- {ivoryos-1.2.2.dist-info → ivoryos-1.2.3.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{% extends 'base.html' %}
|
|
2
|
+
{% block title %}IvoryOS | Login{% endblock %}
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
{% block body %}
|
|
6
|
+
<div class= "login">
|
|
7
|
+
<div class="bg-white rounded shadow-sm flex-fill">
|
|
8
|
+
<div class="p-4" style="align-items: center">
|
|
9
|
+
<h5>Log in</h5>
|
|
10
|
+
<form role="form" method='POST' name="login" action="{{ url_for('auth.login') }}">
|
|
11
|
+
<div class="input-group mb-3">
|
|
12
|
+
<label class="input-group-text" for="username">Username</label>
|
|
13
|
+
<input class="form-control" type="text" id="username" name="username">
|
|
14
|
+
</div>
|
|
15
|
+
<div class="input-group mb-3">
|
|
16
|
+
<label class="input-group-text" for="password">Password</label>
|
|
17
|
+
<input class="form-control" type="password" id="password" name="password">
|
|
18
|
+
</div>
|
|
19
|
+
<button type="submit" class="btn btn-secondary" name="login" style="width: 100%;">login</button>
|
|
20
|
+
</form>
|
|
21
|
+
<p class="message">Not registered? <a href="{{ url_for('auth.signup') }}">Create a new account</a>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
{% endblock %}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{% extends 'base.html' %}
|
|
2
|
+
{% block title %}IvoryOS | Signup{% endblock %}
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
{% block body %}
|
|
6
|
+
<div class= "login">
|
|
7
|
+
<div class="bg-white rounded shadow-sm flex-fill">
|
|
8
|
+
<div class="p-4" style="align: center">
|
|
9
|
+
<h5>Create a new account</h5>
|
|
10
|
+
<form role="form" method='POST' name="signup" action="{{ url_for('auth.signup') }}">
|
|
11
|
+
|
|
12
|
+
<div class="input-group mb-3">
|
|
13
|
+
<label class="input-group-text" for="username">Username</label>
|
|
14
|
+
<input class="form-control" type="text" id="username" name="username" required>
|
|
15
|
+
</div>
|
|
16
|
+
<div class="input-group mb-3">
|
|
17
|
+
<label class="input-group-text" for="password">Password</label>
|
|
18
|
+
<input class="form-control" type="password" id="password" name="password" required>
|
|
19
|
+
</div>
|
|
20
|
+
{# <div class="input-group mb-3">#}
|
|
21
|
+
{# <label class="input-group-text" for="confirm_password">Confirm Password</label>#}
|
|
22
|
+
{# <input class="form-control" type="confirm_password" id="confirm_password" name="confirm_password">#}
|
|
23
|
+
{# </div>#}
|
|
24
|
+
|
|
25
|
+
<button type="submit" class="btn btn-secondary" name="login" style="width: 100%;">Sign up</button>
|
|
26
|
+
</form>
|
|
27
|
+
<p class="message" >Already registered? <a href="{{ url_for('auth.login') }}">Sign In</a></p>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
{% endblock %}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
{% extends 'base.html' %}
|
|
2
|
+
{% block title %}IvoryOS | Controllers {% endblock %}
|
|
3
|
+
|
|
4
|
+
{% block body %}
|
|
5
|
+
<div id="overlay" class="overlay">
|
|
6
|
+
<div>
|
|
7
|
+
<h3 id="overlay-text"></h3>
|
|
8
|
+
<div class="spinner-border" role="status"></div>
|
|
9
|
+
</div>
|
|
10
|
+
</div>
|
|
11
|
+
<div class="container-fluid">
|
|
12
|
+
<div class="row">
|
|
13
|
+
<!-- Sidebar: Instruments -->
|
|
14
|
+
<nav class="col-md-3 col-lg-2 d-md-block bg-light sidebar py-3" style="height: 100vh; overflow-y: auto; position: sticky; top: 0;">
|
|
15
|
+
<div class="sidebar-sticky">
|
|
16
|
+
<!-- Deck Instruments -->
|
|
17
|
+
<div class="mb-4">
|
|
18
|
+
<div class="list-group">
|
|
19
|
+
{% for inst in defined_variables %}
|
|
20
|
+
<a class="list-group-item list-group-item-action d-flex align-items-center {% if instrument == inst %}active bg-primary text-white border-0{% else %}bg-light{% endif %}"
|
|
21
|
+
href="{{ url_for('control.deck_controllers') }}?instrument={{ inst }}"
|
|
22
|
+
style="border-radius: 0.5rem; margin-bottom: 0.5rem; transition: background 0.2s;">
|
|
23
|
+
<span class="flex-grow-1">{{ inst | format_name }}</span>
|
|
24
|
+
{% if instrument == inst %}
|
|
25
|
+
<span class="ms-auto">></span>
|
|
26
|
+
{% endif %}
|
|
27
|
+
</a>
|
|
28
|
+
{% endfor %}
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
<!-- Temp Instruments -->
|
|
35
|
+
{% if temp_variables %}
|
|
36
|
+
<div class="mb-4">
|
|
37
|
+
<h6 class="fw-bold text-secondary mb-2" style="letter-spacing: 1px;">Temp Instruments</h6>
|
|
38
|
+
<div class="list-group">
|
|
39
|
+
{% for inst in temp_variables %}
|
|
40
|
+
<a class="list-group-item list-group-item-action d-flex align-items-center {% if instrument == inst %}active bg-warning text-dark border-0{% else %}bg-light{% endif %}"
|
|
41
|
+
href="{{ url_for('control.deck_controllers') }}?instrument={{ inst }}"
|
|
42
|
+
style="border-radius: 0.5rem; margin-bottom: 0.5rem; transition: background 0.2s;">
|
|
43
|
+
<span class="flex-grow-1">{{ inst | format_name }}</span>
|
|
44
|
+
{% if instrument == inst %}
|
|
45
|
+
<span class="ms-auto">></span>
|
|
46
|
+
{% endif %}
|
|
47
|
+
</a>
|
|
48
|
+
{% endfor %}
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
{% endif %}
|
|
52
|
+
<!-- Action Buttons -->
|
|
53
|
+
<div class="mb-4">
|
|
54
|
+
<a href="{{ url_for('control.file.download_proxy', filetype='proxy') }}" class="btn btn-outline-primary w-100 mb-2">
|
|
55
|
+
Download proxy
|
|
56
|
+
</a>
|
|
57
|
+
<a href="{{ url_for('control.temp.new_controller') }}" class="btn btn-outline-success w-100">
|
|
58
|
+
New connection
|
|
59
|
+
</a>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
</nav>
|
|
63
|
+
|
|
64
|
+
<!-- Main: Method Cards -->
|
|
65
|
+
<main class="col-md-9 ms-sm-auto col-lg-10 px-md-4" style="height: 100vh; overflow-y: auto;">
|
|
66
|
+
{% if instrument%}
|
|
67
|
+
{# <h2 class="text-secondary">{{ instrument }} controller</h2>#}
|
|
68
|
+
<div class="grid-container" id="sortable-grid" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); gap: 1rem; width: 100%;">
|
|
69
|
+
{% set hidden = session.get('hidden_functions', {}) %}
|
|
70
|
+
{% set hidden_instrument = hidden.get(instrument, []) %}
|
|
71
|
+
{% for function, form in forms.items() %}
|
|
72
|
+
{% if function not in hidden_instrument %}
|
|
73
|
+
<div class="card" id="{{function}}" style="margin: 0;">
|
|
74
|
+
<div class="bg-white rounded shadow-sm h-100">
|
|
75
|
+
<i class="bi bi-info-circle ms-2" data-bs-toggle="tooltip" data-bs-placement="top" title='{{ form.hidden_name.description or "Docstring is not available" }}' ></i>
|
|
76
|
+
<a href="{{ url_for('control.hide_function', instrument=instrument, function=function) }}"
|
|
77
|
+
data-method="patch" data-payload='{"hidden":true}' class="toggle-visibility">
|
|
78
|
+
<i style="float: right;" class="bi bi-eye-slash-fill text-muted" title="Hide function"></i>
|
|
79
|
+
</a>
|
|
80
|
+
<div class="form-control" style="border: none">
|
|
81
|
+
<form role="form" method='POST' name="{{function}}" id="{{function}}" action="{{ url_for('control.deck_controllers') }}?instrument={{ instrument }}">
|
|
82
|
+
<div class="form-group">
|
|
83
|
+
{{ form.hidden_tag() }}
|
|
84
|
+
{% for field in form %}
|
|
85
|
+
{% if field.type not in ['CSRFTokenField', 'HiddenField'] %}
|
|
86
|
+
<div class="input-group mb-3">
|
|
87
|
+
<label class="input-group-text">{{ field.label.text | format_name }}</label>
|
|
88
|
+
{% if field.type == "SubmitField" %}
|
|
89
|
+
{{ field(class="btn btn-dark") }}
|
|
90
|
+
{% elif field.type == "BooleanField" %}
|
|
91
|
+
{{ field(class="form-check-input") }}
|
|
92
|
+
{% else %}
|
|
93
|
+
{{ field(class="form-control") }}
|
|
94
|
+
{% endif %}
|
|
95
|
+
</div>
|
|
96
|
+
{% endif %}
|
|
97
|
+
{% endfor %}
|
|
98
|
+
</div>
|
|
99
|
+
<div class="input-group mb-3">
|
|
100
|
+
<button type="submit" name="{{ function }}" id="{{ function }}" class="form-control" style="background-color: #a5cece;">
|
|
101
|
+
{{ function | format_name }}
|
|
102
|
+
</button>
|
|
103
|
+
</div>
|
|
104
|
+
</form>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
{% endif %}
|
|
109
|
+
{% endfor %}
|
|
110
|
+
</div>
|
|
111
|
+
<!-- Hidden functions accordion -->
|
|
112
|
+
<div class="accordion accordion-flush" id="accordionActions" >
|
|
113
|
+
<div class="accordion-item">
|
|
114
|
+
<h4 class="accordion-header">
|
|
115
|
+
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#hidden">
|
|
116
|
+
Hidden functions
|
|
117
|
+
</button>
|
|
118
|
+
</h4>
|
|
119
|
+
</div>
|
|
120
|
+
<div id="hidden" class="accordion-collapse collapse" data-bs-parent="#accordionActions">
|
|
121
|
+
<div class="accordion-body">
|
|
122
|
+
{% for function in hidden_instrument %}
|
|
123
|
+
<div>
|
|
124
|
+
{{ function }}
|
|
125
|
+
<a href="{{ url_for('control.hide_function', instrument=instrument, function=function) }}"
|
|
126
|
+
data-method="patch" data-payload='{"hidden":false}' class="toggle-visibility">
|
|
127
|
+
<i class="bi bi-eye-fill text-success" title="Show function"></i>
|
|
128
|
+
</a>
|
|
129
|
+
</div>
|
|
130
|
+
{% endfor %}
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
134
|
+
<script>
|
|
135
|
+
|
|
136
|
+
document.querySelectorAll('.toggle-visibility').forEach(el => {
|
|
137
|
+
el.addEventListener('click', e => {
|
|
138
|
+
e.preventDefault();
|
|
139
|
+
const url = el.getAttribute('href');
|
|
140
|
+
const payload = JSON.parse(el.getAttribute('data-payload') || '{}');
|
|
141
|
+
fetch(url, {
|
|
142
|
+
method: 'PATCH',
|
|
143
|
+
headers: {
|
|
144
|
+
'Content-Type': 'application/json'
|
|
145
|
+
},
|
|
146
|
+
body: JSON.stringify(payload)
|
|
147
|
+
}).then(response => {
|
|
148
|
+
if (response.ok) {
|
|
149
|
+
location.reload(); // or update the DOM directly
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const saveOrderUrl = `{{ url_for('control.save_order', instrument=instrument) }}`;
|
|
156
|
+
const buttonIds = {{ session['card_order'][instrument] | tojson }};
|
|
157
|
+
</script>
|
|
158
|
+
<script src="{{ url_for('static', filename='js/sortable_card.js') }}"></script>
|
|
159
|
+
<script src="{{ url_for('static', filename='js/overlay.js') }}"></script>
|
|
160
|
+
{% else %}
|
|
161
|
+
<div class="alert alert-info mt-4">Select an instrument to view its methods.</div>
|
|
162
|
+
{% endif %}
|
|
163
|
+
</main>
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
{% endblock %}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
{% extends 'base.html' %}
|
|
2
|
+
{% block title %}IvoryOS | New devices{% endblock %}
|
|
3
|
+
|
|
4
|
+
{% block body %}
|
|
5
|
+
<div class="row">
|
|
6
|
+
<!-- Available Python API -->
|
|
7
|
+
<div class="col-xl-4 col-lg-4 col-md-6 mb-4">
|
|
8
|
+
<div class="card shadow-sm mb-4">
|
|
9
|
+
<div class="card-header">
|
|
10
|
+
<h5 class="mb-0">Available Python API</h5>
|
|
11
|
+
</div>
|
|
12
|
+
<div class="card-body">
|
|
13
|
+
{% for instrument in api_variables %}
|
|
14
|
+
<div class="card mb-2">
|
|
15
|
+
<div class="card-body p-2">
|
|
16
|
+
<a href="{{ url_for('control.temp.new_controller', instrument=instrument) }}" class="text-dark stretched-link">{{ instrument }}</a>
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
{% endfor %}
|
|
20
|
+
<div class="card mt-3">
|
|
21
|
+
<div class="card-body p-2">
|
|
22
|
+
<a data-bs-toggle="modal" href="#importAPI" class="stretched-link">
|
|
23
|
+
<i class="bi bi-folder-plus"></i> Import API
|
|
24
|
+
</a>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
<!-- Connecting Device -->
|
|
32
|
+
<div class="col-xl-5 col-lg-5 col-md-6 mb-4">
|
|
33
|
+
{% if device %}
|
|
34
|
+
{# {{ device }}#}
|
|
35
|
+
<div class="card shadow-sm mb-4">
|
|
36
|
+
<div class="card-header">
|
|
37
|
+
<h5 class="mb-0">Connecting</h5>
|
|
38
|
+
</div>
|
|
39
|
+
<div class="card-body">
|
|
40
|
+
<form role="form" method="POST" name="init" action="{{ url_for('control.temp.new_controller', instrument=instrument) }}">
|
|
41
|
+
<div class="mb-3">
|
|
42
|
+
<label class="form-label" for="device_name">Name this device</label>
|
|
43
|
+
<input class="form-control" type="text" id="device_name" name="device_name" aria-describedby="nameHelpBlock" placeholder="e.g. {{device.__name__}}_1">
|
|
44
|
+
<div id="nameHelpBlock" class="form-text">
|
|
45
|
+
Name your instrument, avoid names that are defined on the right
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
{% for arg in device.__init__.__annotations__ %}
|
|
49
|
+
{% if not arg == "return" %}
|
|
50
|
+
<div class="mb-3">
|
|
51
|
+
<label class="form-label" for="{{arg}}">{{arg}}</label>
|
|
52
|
+
<input class="form-control" type="text" id="{{arg}}" name="{{arg}}"
|
|
53
|
+
placeholder="{{device.__init__.__annotations__[arg].__name__}}"
|
|
54
|
+
value="{{args.parameters[arg].default if not args.parameters[arg].default.__name__ == '_empty' else ''}}">
|
|
55
|
+
{% if device.__init__.__annotations__[arg].__module__ is not in ["builtins", "typing"] %}
|
|
56
|
+
<a role="button" href="{{ url_for('control.temp.new_controller', instrument=device.__init__.__annotations__[arg].__name__) }}" class="btn btn-secondary btn-sm mt-2">Initialize {{device.__init__.__annotations__[arg].__name__}} first</a>
|
|
57
|
+
{% endif %}
|
|
58
|
+
</div>
|
|
59
|
+
{% endif %}
|
|
60
|
+
{% endfor %}
|
|
61
|
+
<button type="submit" class="btn btn-dark">Connect</button>
|
|
62
|
+
</form>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
{% endif %}
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
<!-- Defined Instruments -->
|
|
69
|
+
<div class="col-xl-3 col-lg-3 col-md-6 mb-4">
|
|
70
|
+
<div class="card shadow-sm mb-4">
|
|
71
|
+
<div class="card-header">
|
|
72
|
+
<h5 class="mb-0">Defined Instruments</h5>
|
|
73
|
+
</div>
|
|
74
|
+
<div class="card-body">
|
|
75
|
+
{% if defined_variables %}
|
|
76
|
+
<ul class="list-group">
|
|
77
|
+
{% for instrument in defined_variables %}
|
|
78
|
+
<li class="list-group-item">{{ instrument }}</li>
|
|
79
|
+
{% endfor %}
|
|
80
|
+
</ul>
|
|
81
|
+
{% else %}
|
|
82
|
+
<span class="text-muted">No instruments defined.</span>
|
|
83
|
+
{% endif %}
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
<!-- Import API Modal -->
|
|
90
|
+
<div class="modal fade" id="importAPI" tabindex="-1" aria-labelledby="importModal" aria-hidden="true">
|
|
91
|
+
<div class="modal-dialog">
|
|
92
|
+
<div class="modal-content">
|
|
93
|
+
<div class="modal-header">
|
|
94
|
+
<h1 class="modal-title fs-5" id="importModal">Import API by file path</h1>
|
|
95
|
+
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
96
|
+
</div>
|
|
97
|
+
<form method="POST" action="{{ url_for('control.temp.import_api') }}" enctype="multipart/form-data">
|
|
98
|
+
<div class="modal-body">
|
|
99
|
+
<div class="mb-3">
|
|
100
|
+
<label class="form-label" for="filepath">File Path:</label>
|
|
101
|
+
<input type="text" class="form-control" name="filepath" id="filepath">
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
<div class="modal-footer">
|
|
105
|
+
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
|
106
|
+
<button type="submit" class="btn btn-primary">Save</button>
|
|
107
|
+
</div>
|
|
108
|
+
</form>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
{% endblock %}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<div class="card mb-2 {{ 'border-danger text-danger bg-light' if step.run_error else 'border-secondary' }}">
|
|
2
|
+
<div class="card-body p-2">
|
|
3
|
+
<strong>{{ step.method_name | format_name }}</strong>
|
|
4
|
+
<small class="text-muted">
|
|
5
|
+
<i class="fas fa-play-circle me-1"></i> Start: {{ step.start_time.strftime('%H:%M:%S') if step.start_time else 'N/A' }}
|
|
6
|
+
<i class="fas fa-stop-circle ms-2 me-1"></i> End: {{ step.end_time.strftime('%H:%M:%S') if step.end_time else 'N/A' }}
|
|
7
|
+
<!-- {% if step.run_error %}
|
|
8
|
+
<i class="fas fa-stop-circle ms-2 me-1"></i> Error: {{ step.run_error if step.run_error else 'N/A' }}
|
|
9
|
+
{% endif %} -->
|
|
10
|
+
</small>
|
|
11
|
+
<!-- <small>Error: {{ step.run_error }}</small> -->
|
|
12
|
+
</div>
|
|
13
|
+
</div>
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
{% extends 'base.html' %}
|
|
2
|
+
|
|
3
|
+
{% block title %}IvoryOS | Design Database{% endblock %}
|
|
4
|
+
{% block body %}
|
|
5
|
+
<div class="div">
|
|
6
|
+
<form id="search" style="display: inline-block;float: right;" action="{{url_for('data.list_workflows',deck_name=deck_name)}}" method="GET">
|
|
7
|
+
<div class="input-group">
|
|
8
|
+
<div class="form-outline">
|
|
9
|
+
<input type="search" name="keyword" id="keyword" class="form-control" placeholder="Search workflows...">
|
|
10
|
+
</div>
|
|
11
|
+
<button type="submit" class="btn btn-primary">
|
|
12
|
+
<i class="bi bi-search"></i>
|
|
13
|
+
</button>
|
|
14
|
+
</div>
|
|
15
|
+
</form>
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<table class="table table-hover" id="workflowResultLibrary">
|
|
19
|
+
<thead>
|
|
20
|
+
<tr>
|
|
21
|
+
<th scope="col">Workflow name</th>
|
|
22
|
+
<th scope="col">Workflow ID</th>
|
|
23
|
+
<th scope="col">Start time</th>
|
|
24
|
+
<th scope="col">End time</th>
|
|
25
|
+
<th scope="col">Data</th>
|
|
26
|
+
</tr>
|
|
27
|
+
</thead>
|
|
28
|
+
<tbody>
|
|
29
|
+
{% for workflow in workflows %}
|
|
30
|
+
<tr>
|
|
31
|
+
<td><a href="{{ url_for('data.workflow_logs', workflow_id=workflow.id) }}">{{ workflow.name }}</a></td>
|
|
32
|
+
<td>{{ workflow.id }}</td>
|
|
33
|
+
<td>{{ workflow.start_time.strftime("%Y-%m-%d %H:%M:%S") if workflow.start_time else '' }}</td>
|
|
34
|
+
<td>{{ workflow.end_time.strftime("%Y-%m-%d %H:%M:%S") if workflow.end_time else '' }}</td>
|
|
35
|
+
|
|
36
|
+
<td>
|
|
37
|
+
{% if workflow.data_path %}
|
|
38
|
+
<a href="{{ url_for('data.download_results', filename=workflow.data_path) }}">{{ workflow.data_path }}</a>
|
|
39
|
+
{% endif %}
|
|
40
|
+
</td>
|
|
41
|
+
<td>
|
|
42
|
+
{% if session['user'] == 'admin' or session['user'] == workflow.author %}
|
|
43
|
+
{# <a href="{{ url_for('data.delete_workflow_data', workflow_id=workflow.id) }}">delete</a>#}
|
|
44
|
+
<a href="#"
|
|
45
|
+
class="text-danger"
|
|
46
|
+
data-delete-url="{{ url_for('data.delete_workflow_record', workflow_id=workflow.id) }}"
|
|
47
|
+
onclick="deleteWorkflow(this); return false;">
|
|
48
|
+
Delete
|
|
49
|
+
</a>
|
|
50
|
+
{% else %}
|
|
51
|
+
<a class="disabled-link">delete</a>
|
|
52
|
+
{% endif %}
|
|
53
|
+
</td>
|
|
54
|
+
</tr>
|
|
55
|
+
{% endfor %}
|
|
56
|
+
</tbody>
|
|
57
|
+
</table>
|
|
58
|
+
|
|
59
|
+
{# paging#}
|
|
60
|
+
<div class="pagination justify-content-center">
|
|
61
|
+
<div class="page-item {{ 'disabled' if not workflows.has_prev else '' }}">
|
|
62
|
+
<a class="page-link" href="{{ url_for('data.list_workflows', page=workflows.prev_num) }}">Previous</a>
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
{% for num in workflows.iter_pages() %}
|
|
66
|
+
{% if num %}
|
|
67
|
+
<div class="page-item {{ 'active' if num == workflows.page else '' }}">
|
|
68
|
+
<a class="page-link" href="{{ url_for('data.list_workflows', page=num) }}">{{ num }}</a>
|
|
69
|
+
</div>
|
|
70
|
+
{% else %}
|
|
71
|
+
<div class="page-item disabled">
|
|
72
|
+
<span class="page-link">…</span>
|
|
73
|
+
</div>
|
|
74
|
+
{% endif %}
|
|
75
|
+
{% endfor %}
|
|
76
|
+
|
|
77
|
+
<div class="page-item {{ 'disabled' if not workflows.has_next else '' }}">
|
|
78
|
+
<a class="page-link" href="{{ url_for('data.list_workflows', page=workflows.next_num) }}">Next</a>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
<div id="steps-container"></div>
|
|
83
|
+
|
|
84
|
+
<script>
|
|
85
|
+
function showSteps(workflowId) {
|
|
86
|
+
fetch(`/workflow_steps/${workflowId}`)
|
|
87
|
+
.then(response => response.json())
|
|
88
|
+
.then(data => {
|
|
89
|
+
const container = document.getElementById('steps-container');
|
|
90
|
+
container.innerHTML = ''; // Clear previous content
|
|
91
|
+
const stepsList = document.createElement('ul');
|
|
92
|
+
|
|
93
|
+
data.steps.forEach(step => {
|
|
94
|
+
const li = document.createElement('li');
|
|
95
|
+
li.innerHTML = `
|
|
96
|
+
<strong>Step: </strong> ${step.method_name} <br>
|
|
97
|
+
<strong>Start Time:</strong> ${step.start_time} <br>
|
|
98
|
+
<strong>End Time:</strong> ${step.end_time} <br>
|
|
99
|
+
<strong>Human Intervention:</strong> ${step.run_error ? 'Yes' : 'No'}
|
|
100
|
+
`;
|
|
101
|
+
stepsList.appendChild(li);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
container.appendChild(stepsList);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
</script>
|
|
108
|
+
<script src="{{ url_for('static', filename='js/db_delete.js') }}"></script>
|
|
109
|
+
{% endblock %}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
{% extends 'base.html' %}
|
|
2
|
+
|
|
3
|
+
{% block title %}IvoryOS | Experiment Results{% endblock %}
|
|
4
|
+
|
|
5
|
+
{% block body %}
|
|
6
|
+
<style>
|
|
7
|
+
.vis-time-axis .vis-text.vis-minor,
|
|
8
|
+
.vis-time-axis .vis-text.vis-major {
|
|
9
|
+
color: #666;
|
|
10
|
+
}
|
|
11
|
+
.vis-item.stop {
|
|
12
|
+
background-color: red;
|
|
13
|
+
color: white;
|
|
14
|
+
border: none;
|
|
15
|
+
font-weight: bold;
|
|
16
|
+
}
|
|
17
|
+
</style>
|
|
18
|
+
|
|
19
|
+
<div id="timeline"></div>
|
|
20
|
+
|
|
21
|
+
<script src="https://unpkg.com/vis-timeline@latest/standalone/umd/vis-timeline-graph2d.min.js"></script>
|
|
22
|
+
<link href="https://unpkg.com/vis-timeline@latest/styles/vis-timeline-graph2d.min.css" rel="stylesheet"/>
|
|
23
|
+
|
|
24
|
+
<h1>Experiment Step View</h1>
|
|
25
|
+
|
|
26
|
+
<div id="visualization"></div>
|
|
27
|
+
|
|
28
|
+
<script type="text/javascript">
|
|
29
|
+
var container = document.getElementById('visualization');
|
|
30
|
+
|
|
31
|
+
const items = [
|
|
32
|
+
{% if grouped.prep %}
|
|
33
|
+
{
|
|
34
|
+
id: 'prep',
|
|
35
|
+
content: 'Prep Phase',
|
|
36
|
+
start: '{{ grouped.prep[0].start_time }}',
|
|
37
|
+
end: '{{ grouped.prep[-1].end_time }}',
|
|
38
|
+
className: 'prep',
|
|
39
|
+
group: 'prep'
|
|
40
|
+
},
|
|
41
|
+
{% endif %}
|
|
42
|
+
|
|
43
|
+
{% for repeat_index, step_list in grouped.script.items()|sort %}
|
|
44
|
+
{
|
|
45
|
+
id: 'iter{{ repeat_index }}',
|
|
46
|
+
content: 'Iteration {{ repeat_index }}',
|
|
47
|
+
start: '{{ step_list[0].start_time }}',
|
|
48
|
+
end: '{{ step_list[-1].end_time }}',
|
|
49
|
+
className: 'script',
|
|
50
|
+
group: 'iter{{ repeat_index }}'
|
|
51
|
+
},
|
|
52
|
+
{% for step in step_list %}
|
|
53
|
+
{% if step.method_name == "stop" %}
|
|
54
|
+
{
|
|
55
|
+
id: 'stop-{{ step.id }}',
|
|
56
|
+
content: '🛑 Stop',
|
|
57
|
+
start: '{{ step.start_time }}',
|
|
58
|
+
type: 'point',
|
|
59
|
+
className: 'stop',
|
|
60
|
+
group: 'iter{{ repeat_index }}'
|
|
61
|
+
},
|
|
62
|
+
{% endif %}
|
|
63
|
+
{% endfor %}
|
|
64
|
+
{% endfor %}
|
|
65
|
+
|
|
66
|
+
{% if grouped.cleanup %}
|
|
67
|
+
{
|
|
68
|
+
id: 'cleanup',
|
|
69
|
+
content: 'Cleanup Phase',
|
|
70
|
+
start: '{{ grouped.cleanup[0].start_time }}',
|
|
71
|
+
end: '{{ grouped.cleanup[-1].end_time }}',
|
|
72
|
+
className: 'cleanup',
|
|
73
|
+
group: 'cleanup'
|
|
74
|
+
|
|
75
|
+
},
|
|
76
|
+
{% endif %}
|
|
77
|
+
];
|
|
78
|
+
|
|
79
|
+
const groups = [
|
|
80
|
+
{% if grouped.prep %}{ id: 'prep', content: 'Prep' },{% endif %}
|
|
81
|
+
{% for repeat_index in grouped.script.keys()|sort %}{ id: 'iter{{ repeat_index }}', content: 'Iteration {{ repeat_index }}' },{% endfor %}
|
|
82
|
+
{% if grouped.cleanup %}{ id: 'cleanup', content: 'Cleanup' },{% endif %}
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
var options = {
|
|
86
|
+
clickToUse: true,
|
|
87
|
+
stack: false, // important to keep point within group row
|
|
88
|
+
horizontalScroll: true,
|
|
89
|
+
zoomKey: 'ctrlKey'
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
// Initialize your timeline with the sorted groups
|
|
93
|
+
const timeline = new vis.Timeline(container, items, groups, options);
|
|
94
|
+
|
|
95
|
+
timeline.on('select', function (props) {
|
|
96
|
+
const id = props.items[0];
|
|
97
|
+
if (id && id.startsWith('iter')) {
|
|
98
|
+
const card = document.getElementById('card-' + id);
|
|
99
|
+
if (card) {
|
|
100
|
+
const yOffset = -80;
|
|
101
|
+
const y = card.getBoundingClientRect().top + window.pageYOffset + yOffset;
|
|
102
|
+
window.scrollTo({ top: y, behavior: 'smooth' });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
</script>
|
|
107
|
+
|
|
108
|
+
<h2>Workflow: {{ workflow.name }}</h2>
|
|
109
|
+
|
|
110
|
+
{% if grouped.prep %}
|
|
111
|
+
<h4 class="mt-4">Prep Phase</h4>
|
|
112
|
+
{% for step in grouped.prep %}
|
|
113
|
+
{% include "components/step_card.html" %}
|
|
114
|
+
{% endfor %}
|
|
115
|
+
{% endif %}
|
|
116
|
+
|
|
117
|
+
{% for repeat_index, step_list in grouped.script.items()|sort %}
|
|
118
|
+
<h4 class="mt-4" id="card-iter{{ repeat_index }}">Iteration {{ repeat_index }}</h4>
|
|
119
|
+
{% for step in step_list %}
|
|
120
|
+
{% include "components/step_card.html" %}
|
|
121
|
+
{% endfor %}
|
|
122
|
+
{% endfor %}
|
|
123
|
+
|
|
124
|
+
{% if grouped.cleanup %}
|
|
125
|
+
<h4 class="mt-4">Cleanup Phase</h4>
|
|
126
|
+
{% for step in grouped.cleanup %}
|
|
127
|
+
{% include "components/step_card.html" %}
|
|
128
|
+
{% endfor %}
|
|
129
|
+
{% endif %}
|
|
130
|
+
{% endblock %}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{# Action form component #}
|
|
2
|
+
<div class="accordion-item design-control" draggable="true">
|
|
3
|
+
<h2 class="accordion-header">
|
|
4
|
+
<button class="accordion-button collapsed draggable-action"
|
|
5
|
+
type="button" data-bs-toggle="collapse"
|
|
6
|
+
data-bs-target="#{{name}}" aria-expanded="false"
|
|
7
|
+
aria-controls="collapseExample"
|
|
8
|
+
data-action="{{ name }}">
|
|
9
|
+
{{ name | format_name }}
|
|
10
|
+
</button>
|
|
11
|
+
</h2>
|
|
12
|
+
<div id="{{name}}" class="accordion-collapse collapse" data-bs-parent="#accordionActions">
|
|
13
|
+
<div class="accordion-body">
|
|
14
|
+
<form role="form" method='POST' action="{{ url_for('design.methods_handler', instrument=instrument) }}"
|
|
15
|
+
name="add" id="add-{{name}}" onsubmit="addMethodToDesign(event, this); return false;">
|
|
16
|
+
<div class="form-group">
|
|
17
|
+
{{ form.hidden_tag() }}
|
|
18
|
+
{% for field in form %}
|
|
19
|
+
{% if field.type not in ['CSRFTokenField', 'HiddenField'] %}
|
|
20
|
+
<div class="input-group mb-3">
|
|
21
|
+
<label class="input-group-text">{{ field.label.text | format_name }}</label>
|
|
22
|
+
{% if field.type == "SubmitField" %}
|
|
23
|
+
{{ field(class="btn btn-dark") }}
|
|
24
|
+
{% elif field.type == "BooleanField" %}
|
|
25
|
+
{{ field(class="form-check-input") }}
|
|
26
|
+
{% elif field.type == "FlexibleEnumField" %}
|
|
27
|
+
<input type="text" id="{{ field.id }}" name="{{ field.name }}" value="{{ field.data }}"
|
|
28
|
+
list="{{ field.id }}_options" placeholder="{{ field.render_kw.placeholder if field.render_kw and field.render_kw.placeholder }}"
|
|
29
|
+
class="form-control">
|
|
30
|
+
<datalist id="{{ field.id }}_options">
|
|
31
|
+
{% for key in field.choices %}
|
|
32
|
+
<option value="{{ key }}">{{ key }}</option>
|
|
33
|
+
{% endfor %}
|
|
34
|
+
</datalist>
|
|
35
|
+
{% else %}
|
|
36
|
+
{{ field(class="form-control") }}
|
|
37
|
+
{% endif %}
|
|
38
|
+
</div>
|
|
39
|
+
{% endif %}
|
|
40
|
+
{% endfor %}
|
|
41
|
+
</div>
|
|
42
|
+
<button type="submit" class="btn btn-dark">Add</button>
|
|
43
|
+
{% if 'hidden_name' in form %}
|
|
44
|
+
<i class="bi bi-info-circle ms-2" data-bs-toggle="tooltip" data-bs-placement="top"
|
|
45
|
+
title='{{ form.hidden_name.description or "Docstring is not available" }}'>
|
|
46
|
+
</i>
|
|
47
|
+
{% else %}
|
|
48
|
+
<!-- handle info tooltip for flow control / workflows -->
|
|
49
|
+
{% endif %}
|
|
50
|
+
</form>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|