attempt at following a file
This commit is contained in:
parent
c3be6dd7ef
commit
75da26dbb9
16
oshipka.sh
16
oshipka.sh
@ -8,11 +8,21 @@ echo "oshipka is at: $OSHIPKA_PATH"
|
||||
|
||||
#!/usr/bin/env bash
|
||||
HELP="
|
||||
Usage $0 [ bootstrap ]
|
||||
Usage $0 [ bootstrap | worker | web ]
|
||||
|
||||
bootstrap [PROJECT_PATH] Create a new project in PROJECT_PATH
|
||||
worker Start worker
|
||||
web Start webapp
|
||||
"
|
||||
|
||||
worker () {
|
||||
celery worker --app=tasks.worker.worker_app --concurrency=1 --loglevel=INFO
|
||||
}
|
||||
|
||||
web () {
|
||||
python run.py
|
||||
}
|
||||
|
||||
bootstrap() {
|
||||
shift
|
||||
PROJECT_PATH=$1
|
||||
@ -48,6 +58,10 @@ command_main() {
|
||||
INITIAL_COMMAND=$1
|
||||
case "$INITIAL_COMMAND" in
|
||||
bootstrap) bootstrap "$@"
|
||||
;;
|
||||
worker) worker "$@"
|
||||
;;
|
||||
web) web "$@"
|
||||
;;
|
||||
*) >&2 echo -e "${HELP}"
|
||||
return 1
|
||||
|
@ -11,6 +11,9 @@ def init_db(app):
|
||||
app.config["SQLALCHEMY_DATABASE_URI"] = SQLALCHEMY_DATABASE_URI
|
||||
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
|
||||
|
||||
from oshipka.webapp import test_bp
|
||||
app.register_blueprint(test_bp)
|
||||
|
||||
db.init_app(app)
|
||||
for dir in MAKEDIRS:
|
||||
os.makedirs(dir, exist_ok=True)
|
||||
|
@ -1,4 +1,19 @@
|
||||
from flask import Flask
|
||||
from flask import Flask, Blueprint
|
||||
from flask_socketio import SocketIO
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config["SECRET_KEY"] = "UNSECRET_KEY....478932fjkdsl"
|
||||
|
||||
socketio = SocketIO(
|
||||
app,
|
||||
cors_allowed_origins=["http://localhost:5000"],
|
||||
)
|
||||
|
||||
test_bp = Blueprint('test_bp', __name__,
|
||||
url_prefix="/test",
|
||||
template_folder='templates',
|
||||
static_folder='static',
|
||||
)
|
||||
|
||||
import oshipka.webapp.tasks_routes
|
||||
import oshipka.webapp.websockets_routes
|
||||
|
10598
oshipka/webapp/static/js/jquery.js
vendored
Normal file
10598
oshipka/webapp/static/js/jquery.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
6620
oshipka/webapp/static/js/socket.io.js
Normal file
6620
oshipka/webapp/static/js/socket.io.js
Normal file
File diff suppressed because it is too large
Load Diff
156
oshipka/webapp/tasks_routes.py
Normal file
156
oshipka/webapp/tasks_routes.py
Normal file
@ -0,0 +1,156 @@
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import threading
|
||||
|
||||
from flask import redirect, request, url_for, jsonify, render_template
|
||||
from flask_socketio import emit
|
||||
from flask_table import Table, LinkCol, Col
|
||||
|
||||
from oshipka.persistance import db
|
||||
from oshipka.webapp import test_bp, socketio
|
||||
from oshipka.worker import worker_app
|
||||
|
||||
from config import TASKS_BUF_DIR
|
||||
|
||||
|
||||
class Task(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
|
||||
name = db.Column(db.Unicode)
|
||||
uuid = db.Column(db.Unicode)
|
||||
kwargs = db.Column(db.Unicode)
|
||||
|
||||
def serialize(self):
|
||||
return dict(
|
||||
name=self.name, uuid=self.uuid, kwargs=json.loads(self.kwargs),
|
||||
)
|
||||
|
||||
|
||||
class TasksTable(Table):
|
||||
uuid = LinkCol('Task', "test_bp.get_task_status", url_kwargs=dict(task_uuid='uuid'))
|
||||
name = Col('name')
|
||||
|
||||
|
||||
def worker_start_task(task_name, task_kwargs):
|
||||
async_task = worker_app.send_task(task_name, [], task_kwargs)
|
||||
task = Task(name=task_name,
|
||||
uuid=async_task.id,
|
||||
kwargs=json.dumps(task_kwargs),
|
||||
)
|
||||
db.session.add(task)
|
||||
db.session.commit()
|
||||
return async_task.id
|
||||
|
||||
|
||||
def get_task_ctx(task_uuid):
|
||||
ctx = {}
|
||||
async_task = worker_app.AsyncResult(id=task_uuid)
|
||||
ctx['async_task'] = {
|
||||
'result': async_task.result,
|
||||
'status': async_task.status,
|
||||
}
|
||||
task = Task.query.filter_by(uuid=async_task.id).first()
|
||||
ctx['task'] = task.serialize()
|
||||
out_filename = os.path.join(TASKS_BUF_DIR, task_uuid)
|
||||
if os.path.exists(out_filename):
|
||||
with open(out_filename) as f:
|
||||
ctx['async_task']['stdout'] = f.read()
|
||||
return ctx
|
||||
|
||||
|
||||
@test_bp.route("/tasks", methods=["GET"])
|
||||
def list_tasks():
|
||||
tasks = Task.query.all()
|
||||
tasks_table = TasksTable(tasks)
|
||||
return render_template("test/tasks.html", tasks_table=tasks_table)
|
||||
|
||||
|
||||
@test_bp.route('/tasks/<task_name>/start', methods=['GET', 'POST'])
|
||||
def start_task(task_name):
|
||||
task_kwargs = {k: v for k, v in request.form.items() if k != 'csrf_token'}
|
||||
async_task_id = worker_start_task(task_name, task_kwargs)
|
||||
return redirect(url_for('test_bp.get_task_status', task_uuid=async_task_id))
|
||||
|
||||
|
||||
@test_bp.route('/tasks/<task_uuid>/status')
|
||||
def get_task_status(task_uuid):
|
||||
ctx = get_task_ctx(task_uuid)
|
||||
return render_template('test/task.html', **ctx)
|
||||
|
||||
|
||||
@test_bp.route('/tasks/<task_uuid>')
|
||||
def get_async_task_result(task_uuid):
|
||||
ctx = get_task_ctx(task_uuid)
|
||||
return jsonify(ctx)
|
||||
|
||||
|
||||
PROCESSES = {}
|
||||
|
||||
|
||||
def start_tail_process(filename):
|
||||
p = subprocess.Popen(['tail', '-F', filename],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
universal_newlines=True,
|
||||
)
|
||||
return p
|
||||
|
||||
|
||||
def tail(process):
|
||||
for line in iter(process.stdout.readline, b''):
|
||||
line = line.strip()
|
||||
print("sending s_tail: {}".format(line))
|
||||
emit("s_tail", {"stdout": line})
|
||||
print("sent s_tail: {}".format((line)))
|
||||
|
||||
|
||||
def kill_process(process):
|
||||
if process:
|
||||
# print('process: killing {}'.format(process))
|
||||
process.kill()
|
||||
print('process: killed {}'.format(process))
|
||||
|
||||
|
||||
def remove_process(uuid):
|
||||
if uuid in PROCESSES:
|
||||
del PROCESSES[uuid]
|
||||
|
||||
|
||||
@socketio.on('connect')
|
||||
def handle_connect():
|
||||
sid = str(request.sid)
|
||||
print("socket.connect: session id: {}".format(sid))
|
||||
emit('s_connect', {
|
||||
"sid": sid,
|
||||
})
|
||||
|
||||
|
||||
@socketio.on('start_tail')
|
||||
def handle_start_tail(json):
|
||||
sid = str(request.sid)
|
||||
task_uuid = json.get("task_uuid")
|
||||
task_out_filename = os.path.join(TASKS_BUF_DIR, task_uuid)
|
||||
process = start_tail_process(task_out_filename)
|
||||
PROCESSES[sid] = ({
|
||||
"sid": sid,
|
||||
"process": process,
|
||||
})
|
||||
print("socket.start_tail: session id: {}".format(sid))
|
||||
emit('s_start_tail', {
|
||||
"sid": sid,
|
||||
"task_uuid": task_uuid,
|
||||
})
|
||||
tail(process)
|
||||
|
||||
|
||||
@socketio.on('disconnect')
|
||||
def handle_disconnect():
|
||||
sid = str(request.sid)
|
||||
process = PROCESSES.get(sid, {}).get("process")
|
||||
print("socket.disconnect: session id: {}".format(sid))
|
||||
kill_process(process)
|
||||
remove_process(process)
|
||||
emit('s_dissconnect', {
|
||||
"sid": sid,
|
||||
})
|
178
oshipka/webapp/templates/test/layout.html
Normal file
178
oshipka/webapp/templates/test/layout.html
Normal file
@ -0,0 +1,178 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<title>Canned</title>
|
||||
<style type="text/css">
|
||||
body {
|
||||
font-family: Open Sans, Arial, sans-serif;
|
||||
color: #444;
|
||||
padding: 0 1em;
|
||||
}
|
||||
|
||||
main {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
nav {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
background: white;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media (min-width: 1400px) {
|
||||
main {
|
||||
margin: 0 150px;
|
||||
}
|
||||
|
||||
aside {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
margin: 0;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
#archive-container {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
input[type=text], textarea {
|
||||
width: 100%;
|
||||
padding: 0.5em;
|
||||
font-family: Open Sans, Arial sans-serif;
|
||||
font-size: 1em;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
textarea {
|
||||
height: 5em;
|
||||
}
|
||||
|
||||
.input-label {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
blockquote > h1 {
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
blockquote > h2 {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
blockquote > h3 {
|
||||
font-size: 1.25em;
|
||||
}
|
||||
|
||||
cite:before {
|
||||
content: "\2014 ";
|
||||
}
|
||||
|
||||
cite {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.flash {
|
||||
padding: 4px;
|
||||
margin: 4px;
|
||||
top: 1em;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.flash.error {
|
||||
border: 1px solid #f22;
|
||||
background-color: #faa;
|
||||
color: #f22;
|
||||
}
|
||||
|
||||
.flash.message, .flash.info {
|
||||
border: 1px solid #28f;
|
||||
background-color: #cef;
|
||||
color: #28f;
|
||||
}
|
||||
|
||||
.flash.success {
|
||||
border: 1px solid #2f8;
|
||||
background-color: #cfe;
|
||||
color: #094;
|
||||
}
|
||||
|
||||
.danger {
|
||||
color: #f22;
|
||||
}
|
||||
|
||||
.pull-right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
pre {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
table {
|
||||
border-spacing: 0;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
table tr {
|
||||
background-color: #fff;
|
||||
border-top: 1px solid #aaa;
|
||||
}
|
||||
|
||||
table tr:nth-child(2n) {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
table th, table td {
|
||||
padding: 6px 12px;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.vertical-padding {
|
||||
height: 80px;
|
||||
}
|
||||
</style>
|
||||
{% block style %}{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
<nav>
|
||||
{% include "test/navigation.html" %}
|
||||
</nav>
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
{% for category, message in messages %}
|
||||
<div class="flash {{ category }}">{{ message }}</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
<div class="vertical-padding"></div>
|
||||
<aside>
|
||||
{% block aside %}{% endblock %}
|
||||
</aside>
|
||||
<main>
|
||||
{% if ctx and ctx.messages %}
|
||||
{% for message in ctx.messages %}
|
||||
<div class="flash {{ message.type.value }}">
|
||||
<a href="/messages/{{ message.id }}/dismiss"
|
||||
style="float:right;">x</a>
|
||||
{{ message.body|safe }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
<script src="{{ url_for('test_bp.static', filename='js/jquery.js') }}"></script>
|
||||
{% block script %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
3
oshipka/webapp/templates/test/navigation.html
Normal file
3
oshipka/webapp/templates/test/navigation.html
Normal file
@ -0,0 +1,3 @@
|
||||
Test
|
||||
<a href="{{ url_for('test_bp.websockets') }}">websockets</a> |
|
||||
<a href="{{ url_for('test_bp.list_tasks') }}">tasks</a> |
|
45
oshipka/webapp/templates/test/task.html
Normal file
45
oshipka/webapp/templates/test/task.html
Normal file
@ -0,0 +1,45 @@
|
||||
{% extends "test/layout.html" %}
|
||||
|
||||
{% block aside %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<p><strong>Status:</strong>{{ async_task.status }}</p>
|
||||
<p><strong>Result:</strong>{{ async_task.result }}</p>
|
||||
<p><strong>Task ID:</strong><span id="task_uuid">{{ task.uuid }}</span></p>
|
||||
<p><strong>Task name:</strong>{{ task.name }}</p>
|
||||
<p><strong>Task kwargs:</strong>{{ task.kwargs }}</p>
|
||||
<p><strong>Task out:</strong><span id="task_out"></span></p>
|
||||
{% endblock %}
|
||||
|
||||
{% block script %}
|
||||
<script src="{{ url_for('test_bp.static', filename='js/socket.io.js') }}"></script>
|
||||
<script>
|
||||
var task_uuid = $('#task_uuid').text();
|
||||
|
||||
function connectServer() {
|
||||
socket = io.connect();
|
||||
socket.on('s_connect', function (e) {
|
||||
console.log("s_connect");
|
||||
});
|
||||
|
||||
socket.emit("start_tail", {task_uuid: task_uuid});
|
||||
|
||||
socket.on('s_start_tail', function (e) {
|
||||
console.log('server start tail', e);
|
||||
});
|
||||
|
||||
socket.on('s_tail', function (o) {
|
||||
console.log('server tail', o);
|
||||
$('#task_out').text(o.stdout);
|
||||
});
|
||||
|
||||
socket.on('s_disconnect', function (e) {
|
||||
console.log('server disconnected', e);
|
||||
});
|
||||
}
|
||||
|
||||
connectServer()
|
||||
</script>
|
||||
{% endblock %}
|
11
oshipka/webapp/templates/test/tasks.html
Normal file
11
oshipka/webapp/templates/test/tasks.html
Normal file
@ -0,0 +1,11 @@
|
||||
{% extends "test/layout.html" %}
|
||||
|
||||
{% block aside %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<a href="{{ url_for('test_bp.start_task', task_name='long_running_task') }}">Start long_running_task</a>
|
||||
<hr>
|
||||
{{ tasks_table }}
|
||||
{% endblock %}
|
19
oshipka/webapp/templates/test/websockets.html
Normal file
19
oshipka/webapp/templates/test/websockets.html
Normal file
@ -0,0 +1,19 @@
|
||||
{% extends "test/layout.html" %}
|
||||
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
|
||||
{% block script %}
|
||||
<script src="{{ url_for('test_bp.static', filename='js/socket.io.js') }}"></script>
|
||||
<script>
|
||||
var socket = io();
|
||||
socket.on('connect', function () {
|
||||
console.log("SOCKETIO: sending SYN.");
|
||||
socket.emit('SYN', {data: 'SYN'});
|
||||
console.log("SOCKETIO: sent SYN.");
|
||||
});
|
||||
socket.on('SYN-ACK', function (data) {
|
||||
console.log("SOCKETIO: rcvd SYN-ACK: " + data);
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
17
oshipka/webapp/websockets_routes.py
Normal file
17
oshipka/webapp/websockets_routes.py
Normal file
@ -0,0 +1,17 @@
|
||||
from flask import render_template
|
||||
from flask_socketio import emit
|
||||
|
||||
from oshipka.webapp import test_bp, socketio
|
||||
|
||||
|
||||
@test_bp.route('/websockets')
|
||||
def websockets():
|
||||
return render_template("test/websockets.html")
|
||||
|
||||
|
||||
@socketio.on('SYN')
|
||||
def handle_my_custom_namespace_event(json):
|
||||
print('SOCKETIO: rcvd SYN: {}'.format(json))
|
||||
print('SOCKETIO: sending SYN-ACK')
|
||||
emit("SYN-ACK", {"data": 'SYN-ACK'})
|
||||
print('SOCKETIO: sent SYN-ACK')
|
50
oshipka/worker.py
Normal file
50
oshipka/worker.py
Normal file
@ -0,0 +1,50 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
from celery import Celery
|
||||
from celery.signals import task_prerun
|
||||
|
||||
from config import TASKS_IN_DIR, TASKS_PROC_DIR, TASKS_BUF_DIR
|
||||
|
||||
from oshipka.persistance import db
|
||||
|
||||
worker_app = Celery(__name__)
|
||||
worker_app.conf.update({
|
||||
'broker_url': 'filesystem://',
|
||||
'result_backend': "file://{}".format(TASKS_PROC_DIR),
|
||||
'broker_transport_options': {
|
||||
'data_folder_in': TASKS_IN_DIR,
|
||||
'data_folder_out': TASKS_IN_DIR,
|
||||
'data_folder_processed': TASKS_PROC_DIR,
|
||||
},
|
||||
'imports': ('tasks',),
|
||||
'result_persistent': True,
|
||||
'task_serializer': 'json',
|
||||
'result_serializer': 'json',
|
||||
'accept_content': ['json']})
|
||||
|
||||
|
||||
class Unbuffered(object):
|
||||
def __init__(self, stream):
|
||||
self.stream = stream
|
||||
|
||||
def write(self, data):
|
||||
self.stream.write(data)
|
||||
self.stream.flush()
|
||||
|
||||
def writelines(self, datas):
|
||||
self.stream.writelines(datas)
|
||||
self.stream.flush()
|
||||
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self.stream, attr)
|
||||
|
||||
|
||||
@task_prerun.connect
|
||||
def before_task(task_id=None, task=None, *args, **kwargs):
|
||||
from oshipka import app
|
||||
sys.stdout = Unbuffered(open(os.path.join(TASKS_BUF_DIR, task_id), 'w'))
|
||||
sys.stderr = Unbuffered(open(os.path.join(TASKS_BUF_DIR, task_id), 'w'))
|
||||
db.init_app(app)
|
||||
app.app_context().push()
|
||||
app.test_request_context().push()
|
@ -1,8 +1,28 @@
|
||||
amqp==2.5.2
|
||||
Babel==2.8.0
|
||||
billiard==3.6.3.0
|
||||
celery==4.4.2
|
||||
click==7.1.1
|
||||
dnspython==1.16.0
|
||||
eventlet==0.25.1
|
||||
Flask==1.1.1
|
||||
Flask-SQLAlchemy==2.4.1
|
||||
Flask-Babel==1.0.0
|
||||
Flask-Celery==2.4.3
|
||||
Flask-Script==2.0.6
|
||||
Flask-SocketIO==4.2.1
|
||||
Flask-Table==0.5.0
|
||||
greenlet==0.4.15
|
||||
importlib-metadata==1.5.0
|
||||
itsdangerous==1.1.0
|
||||
Jinja2==2.11.1
|
||||
kombu==4.6.8
|
||||
MarkupSafe==1.1.1
|
||||
SQLAlchemy==1.3.15
|
||||
monotonic==1.5
|
||||
pkg-resources==0.0.0
|
||||
python-engineio==3.12.1
|
||||
python-socketio==4.5.0
|
||||
pytz==2019.3
|
||||
six==1.14.0
|
||||
vine==1.3.0
|
||||
Werkzeug==1.0.0
|
||||
zipp==3.1.0
|
||||
|
14
setup.py
14
setup.py
@ -2,18 +2,8 @@ from setuptools import setup, find_packages
|
||||
|
||||
|
||||
def gen_from_file():
|
||||
return [
|
||||
'click==7.1.1',
|
||||
'Flask==1.1.1',
|
||||
'Flask-SQLAlchemy==2.4.1',
|
||||
'itsdangerous==1.1.0',
|
||||
'Jinja2==2.11.1',
|
||||
'MarkupSafe==1.1.1',
|
||||
'SQLAlchemy==1.3.15',
|
||||
'Werkzeug==1.0.0',
|
||||
]
|
||||
# with open("requirements.txt") as f:
|
||||
# return [line.strip() for line in f.readlines()]
|
||||
with open("requirements.txt") as f:
|
||||
return [line.strip() for line in f.readlines()]
|
||||
|
||||
|
||||
setup(name='oshipka',
|
||||
|
Loading…
Reference in New Issue
Block a user