Add readme and colors.
This commit is contained in:
29
README.md
Normal file
29
README.md
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# Why?
|
||||||
|
|
||||||
|
Sometimes I run long scripts on a remote machine and I want to know when they finish. It's
|
||||||
|
not urgent enough that I need a text or toot, but being able to pull up a simple page that
|
||||||
|
shows what is done and not done makes it easy to check periodically.
|
||||||
|
|
||||||
|
# How?
|
||||||
|
|
||||||
|
This tiny Flask API runs on a server (Raspberry Pi, etc.). It has very basic authentication
|
||||||
|
and waits for a curl (or similar) GET request with some small amount of data. The data are
|
||||||
|
displayed on a simple web page.
|
||||||
|
|
||||||
|
The reason for a simple GET request is to make this as code-agnostic as possible. Drop a
|
||||||
|
curl command into R or Python or Go or Ruby or whatever (I assume) and be able to watch
|
||||||
|
whatever data you pass.
|
||||||
|
|
||||||
|
You can either run with the internal Flask development server or something like gunicorn.
|
||||||
|
|
||||||
|
# Other Use Cases?
|
||||||
|
|
||||||
|
I'm sure there are some.
|
||||||
|
|
||||||
|
# Security?
|
||||||
|
|
||||||
|
I'm sure this isn't very secure. It has basic data cleaning, string length limits, and
|
||||||
|
username/password protection. There is no database--all data are kept in memory and will
|
||||||
|
disappear when the server is stopped. Use at your own risk, don't reuse passwords, and
|
||||||
|
obviously don't pass data that can't be shared openly online. The last part includes things
|
||||||
|
like "front_door_unlocked"--use common sense.
|
6
app.py
6
app.py
@ -10,7 +10,8 @@ app = Flask(__name__)
|
|||||||
|
|
||||||
statuses = [
|
statuses = [
|
||||||
{
|
{
|
||||||
'title': 'curl -u matt:python -i http://localhost:5000/log/api/v1.0/new?title="example_status_no_spaces"',
|
'title': 'app_is_running',
|
||||||
|
'color': "green",
|
||||||
'timestamp': datetime.datetime.now().timestamp(),
|
'timestamp': datetime.datetime.now().timestamp(),
|
||||||
'timestamp_readable': datetime.datetime.now()
|
'timestamp_readable': datetime.datetime.now()
|
||||||
}
|
}
|
||||||
@ -38,6 +39,7 @@ def create_status():
|
|||||||
|
|
||||||
status = {
|
status = {
|
||||||
'title': safer_title,
|
'title': safer_title,
|
||||||
|
'color': request.args.get("color"),
|
||||||
'timestamp': datetime.datetime.now().timestamp(),
|
'timestamp': datetime.datetime.now().timestamp(),
|
||||||
'timestamp_readable': datetime.datetime.now()
|
'timestamp_readable': datetime.datetime.now()
|
||||||
}
|
}
|
||||||
@ -57,7 +59,7 @@ def get_statuses_raw():
|
|||||||
@app.route('/', methods=['GET'])
|
@app.route('/', methods=['GET'])
|
||||||
def get_statuses():
|
def get_statuses():
|
||||||
long_agos = [humanfriendly.format_timespan(datetime.datetime.now().timestamp() - status['timestamp']) for status in statuses[::-1]]
|
long_agos = [humanfriendly.format_timespan(datetime.datetime.now().timestamp() - status['timestamp']) for status in statuses[::-1]]
|
||||||
return render_template("list.html",
|
return render_template("index.html",
|
||||||
statuses = statuses[::-1],
|
statuses = statuses[::-1],
|
||||||
long_agos = long_agos)
|
long_agos = long_agos)
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
.statuses tr td {
|
.statuses tr td {
|
||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
|
padding: 5px;
|
||||||
}
|
}
|
39
templates/index.html
Normal file
39
templates/index.html
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link href="/static/css/style.css" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div id="countdown"><span id="countdown_numbers"></span> seconds until refresh</div>
|
||||||
|
<script>
|
||||||
|
(function countdown(remaining) {
|
||||||
|
if(remaining <= 0)
|
||||||
|
location.reload(true);
|
||||||
|
document.getElementById('countdown_numbers').innerHTML = remaining;
|
||||||
|
setTimeout(function(){ countdown(remaining - 1); }, 1000);
|
||||||
|
})(15); // n seconds
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<table class = "statuses">
|
||||||
|
<tr><th>What Happened?</th><th>When Did It Happen?</th><th>How Long Has It Been?</th></tr>
|
||||||
|
{% for status in statuses %}
|
||||||
|
<tr>
|
||||||
|
<td style="background-color: {{ status.color }}">{{ status.title }}</td><td>{{ status.timestamp_readable }}</td><td>{{ long_agos[loop.index0] }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h2>How does this work?</h2>
|
||||||
|
|
||||||
|
<p>Send a curl (or similar) request from any script or code and include your username and
|
||||||
|
password (see app.py, @auth.get_password).</p>
|
||||||
|
|
||||||
|
<code>
|
||||||
|
curl -u username:password -i "http://localhost:5000/new?title=a_status_without_spaces_goes_here"
|
||||||
|
</code>
|
||||||
|
|
||||||
|
<p>The entire list of statuses is kept in memory and limited to 1000 (or whatever you set
|
||||||
|
it to). If your service or server restarts, the list is wiped out.</p>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -1,19 +0,0 @@
|
|||||||
<html>
|
|
||||||
<head>
|
|
||||||
<link href="/static/css/style.css" rel="stylesheet">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<table class = "statuses">
|
|
||||||
<tr><th>What Happened?</th><th>When Did It Happen?</th><th>How Long Has It Been?</th></tr>
|
|
||||||
{% for status in statuses %}
|
|
||||||
<tr>
|
|
||||||
<td>{{ status.title }}</td><td>{{ status.timestamp_readable }}</td><td>{{ long_agos[loop.index0] }}</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</table>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
Reference in New Issue
Block a user