Skip to content

Commit 3bcb13c

Browse files
authored
Merge pull request #85 from qiuosier/job_monitor
Add job monitor.
2 parents d25ddd2 + d236b17 commit 3bcb13c

File tree

4 files changed

+412
-0
lines changed

4 files changed

+412
-0
lines changed

jobs/flask_job_monitor/README.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# OCI Job Monitor UI tool
2+
3+
Incubator project to augment the OCI console with useful functionality to support development of Data Science Jobs.
4+
5+
## How to run
6+
### Requirements
7+
This tool requires `oci>=2.45.1` and `oracle-ads>=2.4.2`.
8+
```
9+
pip install oci oracle-ads flask --upgrade
10+
```
11+
This tool uses OCI API key for authentication. The `DEFAULT` profile in the API key will be used.
12+
13+
### Command Line
14+
To start the Flask app, simply run the following command and open http://127.0.0.1:5000/ with your browser.
15+
```
16+
OCI_PYTHON_SDK_NO_SERVICE_IMPORTS=1 FLASK_APP=job_monitor flask run
17+
```
18+
19+
The dropdown options for selecting compartment ID and project ID does not support tenancy override (e.g. ociodsccust) at the moment. For that, you will need to specify the compartment ID and project ID in the URL:
20+
```
21+
http://127.0.0.1:5000/<COMPARTMENT_OCID>/<PROJECT_OCID>
22+
```
23+
### VS Code Launch Config
24+
The following config can be used in the VS Code `launch.json` to launch the Flask app. You may need to change the value of `FLASK_APP` to your local path if your default directory is not the root of this project.
25+
```
26+
{
27+
"name": "Jobs Monitor",
28+
"type": "python",
29+
"request": "launch",
30+
"module": "flask",
31+
"env": {
32+
"FLASK_APP": "job_monitor.py",
33+
"FLASK_ENV": "development",
34+
"OCI_PYTHON_SDK_NO_SERVICE_IMPORTS": "1",
35+
// "RECORDING": "1",
36+
},
37+
"args": [
38+
"run",
39+
"--no-debugger"
40+
],
41+
"jinja": true
42+
},
43+
```
Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
<!DOCTYPE html>
2+
<html>
3+
4+
<head>
5+
<title>ADS Jobs Monitor</title>
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
7+
<link href="http://netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css" rel="stylesheet" media="screen">
8+
<script src="http://code.jquery.com/jquery-1.10.2.min.js"></script>
9+
<script src="http://netdna.bootstrapcdn.com/bootstrap/3.0.0/js/bootstrap.min.js"></script>
10+
11+
</head>
12+
13+
<body style="padding: 30px;">
14+
<div class="container-fluid">
15+
<h1>Jobs Monitor</h1>
16+
<p>Select compartment and project to show the most recent 20 jobs.</p>
17+
<div class="row">
18+
<div class="col-md-6">
19+
<div class="form-group">
20+
<label for="compartments">Compartment</label>
21+
<select class="form-control" name="compartments" id="compartments">
22+
{% if not compartment_id %}
23+
<option value="" selected="selected">Select Compartment</option>
24+
{% endif %}
25+
{% for compartment in compartments %}
26+
{% if compartment_id == compartment.id %}
27+
<option value="{{ compartment.id }}" selected="selected">{{ compartment.name }}</option>
28+
{% else %}
29+
<option value="{{ compartment.id }}">{{ compartment.name }}</option>
30+
{% endif %}
31+
{% endfor %}
32+
</select>
33+
</div>
34+
</div>
35+
<div class="col-md-6">
36+
<div class="form-group">
37+
<label for="projects">Project</label>
38+
<select class="form-control" name="projects" id="projects">
39+
{% if compartment_id and not project_id %}
40+
<option value="" selected="selected">Select Project</option>
41+
{% endif %}
42+
{% for project in projects %}
43+
{% if project_id == project.id %}
44+
<option value="{{ project.id }}" selected="selected">{{ project.display_name }}</option>
45+
{% else %}
46+
<option value="{{ project.id }}">{{ project.display_name }}</option>
47+
{% endif %}
48+
{% endfor %}
49+
</select>
50+
</div>
51+
</div>
52+
</div>
53+
54+
<hr />
55+
56+
<div class="row">
57+
{% for job in jobs %}
58+
{% for run in job.run_list() %}
59+
<div class="col-lg-4 {{ job.id|replace('.', '') }}">
60+
<div class="panel panel-default run-monitor" id="{{ run.id }}">
61+
62+
<div class="panel-heading">
63+
<div>
64+
<div class="pull-right" style="font-size: larger">
65+
<a href="#" data-toggle="modal" data-target="#modal-details-{{ run.id|replace('.', '') }}">
66+
<span class="glyphicon glyphicon-info-sign"></span>
67+
</a>
68+
<a class="text-danger" href="#" data-toggle="modal" data-target="#modal-delete-{{ run.id|replace('.', '') }}">
69+
<span class="glyphicon glyphicon-remove"></span>
70+
</a>
71+
</div>
72+
<h4>
73+
<a style="color: inherit;" target="_blank" href="https://console.us-ashburn-1.oraclecloud.com/data-science/jobs/{{ job.id }}">
74+
{{ job.name }}
75+
</a>
76+
</h4>
77+
</div>
78+
<div class="clearfix"></div>
79+
<div>
80+
<div class="pull-right">
81+
<p><strong class="run-status">{{ run.lifecycle_state}}</strong></p>
82+
</div>
83+
<h5>{{ run.name }}</h5>
84+
</div>
85+
</div>
86+
<!-- Modal -->
87+
<div id="modal-details-{{ run.id|replace('.', '') }}" class="modal fade" role="dialog">
88+
<div class="modal-dialog" style="width: 70%">
89+
<!-- Modal content-->
90+
<div class="modal-content">
91+
<div class="modal-header">
92+
<button type="button" class="close" data-dismiss="modal">&times;</button>
93+
<h4 class="modal-title">{{ job.name}}</h4>
94+
</div>
95+
<div class="modal-body">
96+
<pre style="overflow-x: scroll;">{{ job|safe }}</pre>
97+
</div>
98+
<div class="modal-footer">
99+
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
100+
</div>
101+
</div>
102+
</div>
103+
</div>
104+
<div id="modal-delete-{{ run.id|replace('.', '') }}" class="modal fade" role="dialog">
105+
<div class="modal-dialog" style="width: 50%">
106+
<!-- Modal content-->
107+
<div class="modal-content">
108+
<div class="modal-header">
109+
<button type="button" class="close" data-dismiss="modal">&times;</button>
110+
<h4 class="modal-title">{{ job.name}}</h4>
111+
</div>
112+
<div class="modal-body">
113+
Delete Job / Job Run? <br />
114+
{{ run.id }}
115+
</div>
116+
<div class="modal-footer">
117+
<button type="button" class="btn btn-danger" onclick="deleteJob('{{ job.id }}')" data-dismiss="modal">Delete Job and Runs</button>
118+
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
119+
</div>
120+
</div>
121+
</div>
122+
</div>
123+
<div class="panel-body" style="font-family: 'Courier New', Courier, monospace; height: 500px; overflow: scroll;">
124+
Loading...
125+
</div>
126+
<div class="panel-footer">
127+
{% if run.log_id %}
128+
<div><a target="_blank" href="https://console.us-ashburn-1.oraclecloud.com/logging/search?searchQuery=search%20%22{{ run.compartment_id }}%22%20%7C%20source%3D%27%2A{{ run.id }}%27%20%7C%20sort%20by%20datetime%20desc">View logs in OCI
129+
console</a></div>
130+
{% endif %}
131+
{{ run.id }} <br>
132+
<span class=".run-status-details">
133+
{{ run.lifecycle_state }}
134+
{% if run.lifecycle_details %}
135+
- {{ run.lifecycle_details }}
136+
{% endif %}
137+
</span>
138+
</div>
139+
</div>
140+
</div>
141+
{% endfor %}
142+
{% endfor %}
143+
</div>
144+
</div>
145+
146+
147+
</body>
148+
<script>
149+
$(document).ready(function() {
150+
// Load logs.
151+
$(".run-monitor").each(function() {
152+
var ocid = this.id;
153+
var outputDiv = $(this).find(".panel-body");
154+
updateLogs(ocid, outputDiv);
155+
})
156+
// Refresh the page to see jobs when project is changed.
157+
$("#projects").change(function() {
158+
var project_ocid = $("#projects").val();
159+
var compartment_ocid = $("#compartments").val();
160+
window.location.href = "/" + compartment_ocid + "/" + project_ocid;
161+
})
162+
// Load the list of project in the compartment.
163+
$("#compartments").change(function() {
164+
var ocid = $("#compartments").val();
165+
$.getJSON("/projects/" + ocid, function(data) {
166+
var project_selector = $("#projects");
167+
project_selector.empty();
168+
project_selector.append('<option value="" selected="selected">Select Project</option>');
169+
data.projects.forEach(element => {
170+
if (element.ocid === "{{ project_id }}") {
171+
project_selector.append('<option value="' + element.ocid + '" selected>' + element.display_name + '</option>');
172+
} else {
173+
project_selector.append('<option value="' + element.ocid + '">' + element.display_name + '</option>');
174+
}
175+
});
176+
})
177+
})
178+
// Trigger the compartment change callback to load the list of projects.
179+
$("#compartments").change();
180+
181+
})
182+
183+
function updateLogs(ocid, outputDiv) {
184+
console.log("Getting logs for " + ocid);
185+
// Get the most recent logs of each job
186+
$.getJSON("/logs/" + ocid, function(data) {
187+
// console.log($("#" + ocid));
188+
outputDiv.html(data.logs.join("<br />"));
189+
// Scroll to the bottom
190+
outputDiv.scrollTop(outputDiv[0].scrollHeight);
191+
parent = outputDiv.closest(".panel");
192+
statusText = parent.find(".run-status");
193+
statusText.text(data.status);
194+
statusDetailsText = parent.find(".run-status-details");
195+
if (data.statusDetails !== null) {
196+
statusDetailsText.text(data.status + " - " + data.statusDetails);
197+
} else {
198+
statusDetailsText.text(data.status);
199+
}
200+
201+
if (data.stopped !== true) {
202+
// Job is running
203+
setTimeout(function() {
204+
updateLogs(ocid, outputDiv)
205+
}, 20000);
206+
setPanelClass(parent, "panel-primary");
207+
} else {
208+
// Job terminated
209+
if (data.status === "SUCCEEDED") {
210+
setPanelClass(parent, "panel-success");
211+
statusText.addClass("text-success");
212+
} else if (data.status === "FAILED") {
213+
setPanelClass(parent, "panel-danger");
214+
statusText.addClass("text-danger");
215+
}
216+
}
217+
})
218+
}
219+
220+
function setPanelClass(panel, newClass) {
221+
panel.removeClass("panel-default");
222+
panel.removeClass("panel-primary");
223+
panel.addClass(newClass);
224+
}
225+
226+
function deleteJob(ocid) {
227+
$.getJSON("/delete/" + ocid, function(data) {
228+
console.log("Deleting " + ocid);
229+
if (data.error === null) {
230+
$("." + ocid.replace(/\./g, "")).remove();
231+
} else {
232+
alert(data.error);
233+
}
234+
235+
});
236+
}
237+
</script>
238+
239+
</html>

0 commit comments

Comments
 (0)