diff --git a/.gitignore b/.gitignore index 689f5e1..4aa1d16 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,9 @@ test/environment test/conf .idea config +instance +doc +venv +lib + diff --git a/utils/flask_tool.py b/utils/flask_tool.py new file mode 100644 index 0000000..e69de29 diff --git a/webdev.sh b/webdev.sh new file mode 100755 index 0000000..3248fc0 --- /dev/null +++ b/webdev.sh @@ -0,0 +1,8 @@ +#!/usr/bin/bash +# script to start development web in webflask - port 5000 +export FLASK_APP=webflask +export FLASK_DEBUG=1 +# flask init-db + +flask run + diff --git a/webflask/__init__.py b/webflask/__init__.py new file mode 100644 index 0000000..3cbc941 --- /dev/null +++ b/webflask/__init__.py @@ -0,0 +1,57 @@ +import os +from flask import Flask + + +def create_app(test_config=None): + # create and configure the app + app = Flask(__name__, instance_relative_config=True) + app.config.from_mapping( + SECRET_KEY='dev', + DATABASE=os.path.join(app.instance_path, 'flaskr.sqlite'), + ) + + if test_config is None: + # load the instance config, if it exists, when not testing + app.config.from_pyfile('config.py', silent=True) + else: + # load the test config if passed in + app.config.from_mapping(test_config) + + # ensure the instance folder exists + try: + os.makedirs(app.instance_path) + except OSError: + pass + + from . import db + db.init_app(app) + from . import auth + from . import index + from . import testcase + from . import testsuite + from . import components + from . import reports + from . import environment + app.register_blueprint(auth.bp) + app.register_blueprint(index.bp) + app.register_blueprint(testcase.bp) + app.register_blueprint(testsuite.bp) + app.register_blueprint(components.bp) + app.register_blueprint(reports.bp) + app.register_blueprint(environment.bp) + + # a simple page that says hello + @app.route('/hello') + def hello(): + return 'Hello, World!' + + return app + + @app.route('/') + def index(): + return 'Index Page' + + @app.route('/index') + def index(): + return 'Index Page 2' + diff --git a/webflask/application.py b/webflask/application.py new file mode 100644 index 0000000..4cc22b8 --- /dev/null +++ b/webflask/application.py @@ -0,0 +1,31 @@ +# https://flask.palletsprojects.com/en/2.0.x/tutorial/views/ +# -------------------------------------------------------------- +import functools + +from flask import ( + Blueprint, flash, g, redirect, render_template, request, session, url_for +) +from werkzeug.security import check_password_hash, generate_password_hash + +from webflask.db import get_db + +bp = Blueprint('testcase', __name__, url_prefix='/testcase') + +@bp.route('/selection', methods=('GET', 'POST')) +def selection(): + if request.method == 'POST': + error = None + + flash(error) + + return render_template('testcase/selection.html') + +@bp.route('/overview', methods=('GET', 'POST')) +def overview(): + if request.method == 'POST': + error = None + + flash(error) + + return render_template('testcase/overview.html') + diff --git a/webflask/auth.py b/webflask/auth.py new file mode 100644 index 0000000..62191d5 --- /dev/null +++ b/webflask/auth.py @@ -0,0 +1,92 @@ +# https://flask.palletsprojects.com/en/2.0.x/tutorial/views/ +# -------------------------------------------------------------- +import functools + +from flask import ( + Blueprint, flash, g, redirect, render_template, request, session, url_for +) +from werkzeug.security import check_password_hash, generate_password_hash + +from webflask.db import get_db + +bp = Blueprint('auth', __name__, url_prefix='/auth') + +@bp.route('/login', methods=('GET', 'POST')) +def login(): + if request.method == 'POST': + username = request.form['username'] + password = request.form['password'] + db = get_db() + error = None + user = db.execute( + 'SELECT * FROM user WHERE username = ?', (username,) + ).fetchone() + + if user is None: + error = 'Incorrect username.' + elif not check_password_hash(user['password'], password): + error = 'Incorrect password.' + + if error is None: + session.clear() + session['user_id'] = user['id'] + return redirect(url_for('index')) + + flash(error) + + return render_template('auth/login.html') + +@bp.route('/register', methods=('GET', 'POST')) +def register(): + if request.method == 'POST': + username = request.form['username'] + password = request.form['password'] + db = get_db() + error = None + + if not username: + error = 'Username is required.' + elif not password: + error = 'Password is required.' + + if error is None: + try: + db.execute( + "INSERT INTO user (username, password) VALUES (?, ?)", + (username, generate_password_hash(password)), + ) + db.commit() + except db.IntegrityError: + error = f"User {username} is already registered." + else: + return redirect(url_for("auth.login")) + + flash(error) + + return render_template('auth/register.html') + +@bp.before_app_request +def load_logged_in_user(): + user_id = session.get('user_id') + + if user_id is None: + g.user = None + else: + g.user = get_db().execute( + 'SELECT * FROM user WHERE id = ?', (user_id,) + ).fetchone() + +@bp.route('/logout') +def logout(): + session.clear() + return redirect(url_for('index')) + +def login_required(view): + @functools.wraps(view) + def wrapped_view(**kwargs): + if g.user is None: + return redirect(url_for('auth.login')) + + return view(**kwargs) + + return wrapped_view \ No newline at end of file diff --git a/webflask/components.py b/webflask/components.py new file mode 100644 index 0000000..de42bb2 --- /dev/null +++ b/webflask/components.py @@ -0,0 +1,31 @@ +# https://flask.palletsprojects.com/en/2.0.x/tutorial/views/ +# -------------------------------------------------------------- +import functools + +from flask import ( + Blueprint, flash, g, redirect, render_template, request, session, url_for +) +from werkzeug.security import check_password_hash, generate_password_hash + +from webflask.db import get_db + +bp = Blueprint('components', __name__, url_prefix='/components') + +@bp.route('/selection', methods=('GET', 'POST')) +def selection(): + if request.method == 'POST': + error = None + + flash(error) + + return render_template('components/selection.html') + +@bp.route('/overview', methods=('GET', 'POST')) +def overview(): + if request.method == 'POST': + error = None + + flash(error) + + return render_template('components/overview.html') + diff --git a/webflask/datest.py b/webflask/datest.py new file mode 100644 index 0000000..4cc22b8 --- /dev/null +++ b/webflask/datest.py @@ -0,0 +1,31 @@ +# https://flask.palletsprojects.com/en/2.0.x/tutorial/views/ +# -------------------------------------------------------------- +import functools + +from flask import ( + Blueprint, flash, g, redirect, render_template, request, session, url_for +) +from werkzeug.security import check_password_hash, generate_password_hash + +from webflask.db import get_db + +bp = Blueprint('testcase', __name__, url_prefix='/testcase') + +@bp.route('/selection', methods=('GET', 'POST')) +def selection(): + if request.method == 'POST': + error = None + + flash(error) + + return render_template('testcase/selection.html') + +@bp.route('/overview', methods=('GET', 'POST')) +def overview(): + if request.method == 'POST': + error = None + + flash(error) + + return render_template('testcase/overview.html') + diff --git a/webflask/db.py b/webflask/db.py new file mode 100644 index 0000000..a94e23c --- /dev/null +++ b/webflask/db.py @@ -0,0 +1,41 @@ +# https://flask.palletsprojects.com/en/2.0.x/tutorial/database/ +# ---------------------------------------------------------------------------- +import sqlite3 + +import click +from flask import current_app, g +from flask.cli import with_appcontext + +def init_db(): + db = get_db() + + with current_app.open_resource('schema.sql') as f: + db.executescript(f.read().decode('utf8')) + +def get_db(): + if 'db' not in g: + g.db = sqlite3.connect( + current_app.config['DATABASE'], + detect_types=sqlite3.PARSE_DECLTYPES + ) + g.db.row_factory = sqlite3.Row + + return g.db + + +def close_db(e=None): + db = g.pop('db', None) + + if db is not None: + db.close() + +@click.command('init-db') +@with_appcontext +def init_db_command(): + """Clear the existing data and create new tables.""" + init_db() + click.echo('Initialized the database.') + +def init_app(app): + app.teardown_appcontext(close_db) + app.cli.add_command(init_db_command) diff --git a/webflask/environment.py b/webflask/environment.py new file mode 100644 index 0000000..01024d9 --- /dev/null +++ b/webflask/environment.py @@ -0,0 +1,31 @@ +# https://flask.palletsprojects.com/en/2.0.x/tutorial/views/ +# -------------------------------------------------------------- +import functools + +from flask import ( + Blueprint, flash, g, redirect, render_template, request, session, url_for +) +from werkzeug.security import check_password_hash, generate_password_hash + +from webflask.db import get_db + +bp = Blueprint('environment', __name__, url_prefix='/environment') + +@bp.route('/selection', methods=('GET', 'POST')) +def selection(): + if request.method == 'POST': + error = None + + flash(error) + + return render_template('environment/selection.html') + +@bp.route('/overview', methods=('GET', 'POST')) +def overview(): + if request.method == 'POST': + error = None + + flash(error) + + return render_template('environment/overview.html') + diff --git a/webflask/index.py b/webflask/index.py new file mode 100644 index 0000000..cceb8aa --- /dev/null +++ b/webflask/index.py @@ -0,0 +1,23 @@ +# https://flask.palletsprojects.com/en/2.0.x/tutorial/views/ +# -------------------------------------------------------------- +import functools + +from flask import ( + Blueprint, flash, g, redirect, render_template, request, session, url_for +) +from werkzeug.security import check_password_hash, generate_password_hash + +from webflask.db import get_db + +bp = Blueprint('index', __name__, url_prefix='/') + +@bp.route('/', methods=('GET', 'POST')) +def index(): + if request.method == 'POST': + error = None + + flash(error) + + return render_template('index.html') + + diff --git a/webflask/reports.py b/webflask/reports.py new file mode 100644 index 0000000..1057510 --- /dev/null +++ b/webflask/reports.py @@ -0,0 +1,31 @@ +# https://flask.palletsprojects.com/en/2.0.x/tutorial/views/ +# -------------------------------------------------------------- +import functools + +from flask import ( + Blueprint, flash, g, redirect, render_template, request, session, url_for +) +from werkzeug.security import check_password_hash, generate_password_hash + +from webflask.db import get_db + +bp = Blueprint('reports', __name__, url_prefix='/reports') + +@bp.route('/selection', methods=('GET', 'POST')) +def selection(): + if request.method == 'POST': + error = None + + flash(error) + + return render_template('reports/selection.html') + +@bp.route('/overview', methods=('GET', 'POST')) +def overview(): + if request.method == 'POST': + error = None + + flash(error) + + return render_template('reports/overview.html') + diff --git a/webflask/schema.sql b/webflask/schema.sql new file mode 100644 index 0000000..be76d7e --- /dev/null +++ b/webflask/schema.sql @@ -0,0 +1,17 @@ +DROP TABLE IF EXISTS user; +DROP TABLE IF EXISTS post; + +CREATE TABLE user ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + username TEXT UNIQUE NOT NULL, + password TEXT NOT NULL +); + +CREATE TABLE post ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + author_id INTEGER NOT NULL, + created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + title TEXT NOT NULL, + body TEXT NOT NULL, + FOREIGN KEY (author_id) REFERENCES user (id) +); diff --git a/webflask/static/script.js b/webflask/static/script.js new file mode 100644 index 0000000..b89be16 --- /dev/null +++ b/webflask/static/script.js @@ -0,0 +1,20 @@ + addEventListener("load", addCollaps); + function addCollaps() { + var coll = document.getElementsByClassName("collapsible"); + for (var c of coll) { + c.addEventListener("click", function() { + this.nextElementSibling.classList.toggle("collapsed"); + }); + } + } + // TODO : kann raus + function loadJson(filename, callback) { + var xhr = XMLHttpRequest(); + xhr.onreadystatechange = function() { + if(this.readyState == 4 && this.status == 200) { + callback(JSON.parse(xhr.responseText)); + } + } + xhr.open(get, filename); + xhr.send(); + } diff --git a/webflask/static/style.css b/webflask/static/style.css new file mode 100644 index 0000000..e89c377 --- /dev/null +++ b/webflask/static/style.css @@ -0,0 +1,31 @@ +html { font-family: sans-serif; background: #eee; padding: 1rem; } +body { max-width: 960px; margin: 0 auto; background: white; } +h1 { font-family: serif; color: #377ba8; margin: 1rem 0; } +a { color: #377ba8; } +hr { border: none; border-top: 1px solid lightgray; } +nav { background: lightgray; display: flex; align-items: center; padding: 0 0.5rem; } +nav h1 { flex: auto; margin: 0; } +nav h1 a { text-decoration: none; padding: 0.25rem 0.5rem; } +nav ul { display: flex; list-style: none; margin: 0; padding: 0; } +nav ul li a, nav ul li span, header .action { display: block; padding: 0.5rem; } +.content { padding: 0 1rem 1rem; } +.content > header { border-bottom: 1px solid lightgray; display: flex; align-items: flex-end; } +.content > header h1 { flex: auto; margin: 1rem 0 0.25rem 0; } +.flash { margin: 1em 0; padding: 1em; background: #cae6f6; border: 1px solid #377ba8; } +.post > header { display: flex; align-items: flex-end; font-size: 0.85em; } +.post > header > div:first-of-type { flex: auto; } +.post > header h1 { font-size: 1.5em; margin-bottom: 0; } +.post .about { color: slategray; font-style: italic; } +.post .body { white-space: pre-line; } +.content:last-child { margin-bottom: 0; } +.content form { margin: 1em 0; display: flex; flex-direction: column; } +.content label { font-weight: bold; margin-bottom: 0.5em; } +.content input, .content textarea { margin-bottom: 1em; } +.content textarea { min-height: 12em; resize: vertical; } +input.danger { color: #cc2f2e; } +input[type=submit] { align-self: start; min-width: 10em; } +.collapsible { background-color: #777; color: white; cursor: pointer; padding: 5px; width: 100%; + border: none; text-align: left; outline: none; font-size: 15px; } +.collapsed { padding: 0 18px; display: none; overflow: hidden; } +.active, .collapsible:hover { background-color: #ccc; } + diff --git a/webflask/templates/application/overview.html b/webflask/templates/application/overview.html new file mode 100644 index 0000000..5dffad5 --- /dev/null +++ b/webflask/templates/application/overview.html @@ -0,0 +1,9 @@ +{% extends 'base.html' %} + +{% block header %} +

{% block title %}Overview{% endblock %}

+{% endblock %} + +{% block content %} +

Hier kommt Inhalt rein

+{% endblock %} diff --git a/webflask/templates/auth/login.html b/webflask/templates/auth/login.html new file mode 100644 index 0000000..c8fcc17 --- /dev/null +++ b/webflask/templates/auth/login.html @@ -0,0 +1,16 @@ +{% extends 'base.html' %} + +{% block header %} +

{% block title %}Log In{% endblock %}

+{% endblock %} + +{% block content %} +
+ + + + + +
+

hiermit sollte ein Workspace geoeffnet werden
- bestehend aus eigenem testdata-repo und components-repo.

+{% endblock %} diff --git a/webflask/templates/auth/register.html b/webflask/templates/auth/register.html new file mode 100644 index 0000000..43cb0c5 --- /dev/null +++ b/webflask/templates/auth/register.html @@ -0,0 +1,17 @@ +{% extends 'base.html' %} + +{% block header %} +

{% block title %}Register{% endblock %}

+{% endblock %} + +{% block content %} +
+ + + + + +
+

Durch das Registrieren sollte ein Workspace angelegt werden
+ - bestehend aus eine testdata-repo und einem components-repo

+{% endblock %} diff --git a/webflask/templates/base.html b/webflask/templates/base.html new file mode 100644 index 0000000..14129a9 --- /dev/null +++ b/webflask/templates/base.html @@ -0,0 +1,40 @@ + + + {% block title %}{% endblock %} - Datest + + + + + + +
+
+ {% block header %}{% endblock %} +
+ {% for message in get_flashed_messages() %} +
{{ message }}
+ {% endfor %} + {% block content %}{% endblock %} +
+ \ No newline at end of file diff --git a/webflask/templates/datest/overview.html b/webflask/templates/datest/overview.html new file mode 100644 index 0000000..5dffad5 --- /dev/null +++ b/webflask/templates/datest/overview.html @@ -0,0 +1,9 @@ +{% extends 'base.html' %} + +{% block header %} +

{% block title %}Overview{% endblock %}

+{% endblock %} + +{% block content %} +

Hier kommt Inhalt rein

+{% endblock %} diff --git a/webflask/templates/environment/overview.html b/webflask/templates/environment/overview.html new file mode 100644 index 0000000..5dffad5 --- /dev/null +++ b/webflask/templates/environment/overview.html @@ -0,0 +1,9 @@ +{% extends 'base.html' %} + +{% block header %} +

{% block title %}Overview{% endblock %}

+{% endblock %} + +{% block content %} +

Hier kommt Inhalt rein

+{% endblock %} diff --git a/webflask/templates/index.html b/webflask/templates/index.html new file mode 100644 index 0000000..cf2d2c6 --- /dev/null +++ b/webflask/templates/index.html @@ -0,0 +1,12 @@ +{% extends 'base.html' %} + +{% block header %} +

{% block title %}Overview{% endblock %}

+{% endblock %} + +{% block content %} +

Hier kommt Inhalt rein:
+ - info Datest allgemein -> ucarmesin.de/joomla + - Bereiche Testfaelle (anzahl) + - letzte Updates (git log)

+{% endblock %} diff --git a/webflask/templates/reports/overview.html b/webflask/templates/reports/overview.html new file mode 100644 index 0000000..5dffad5 --- /dev/null +++ b/webflask/templates/reports/overview.html @@ -0,0 +1,9 @@ +{% extends 'base.html' %} + +{% block header %} +

{% block title %}Overview{% endblock %}

+{% endblock %} + +{% block content %} +

Hier kommt Inhalt rein

+{% endblock %} diff --git a/webflask/templates/testcase/overview.html b/webflask/templates/testcase/overview.html new file mode 100644 index 0000000..7cc689e --- /dev/null +++ b/webflask/templates/testcase/overview.html @@ -0,0 +1,32 @@ +{% extends 'base.html' %} + +{% block header %} +

{% block title %}Overview{% endblock %}

+{% endblock %} + +{% block content %} +

Hier kommt Inhalt rein.
+ Bei angemeldetem User kann auf das eigene Repo schreibend zugegriffen werden, + als Gast nur lesend auf die Vorlage.

+

Statistische Daten zu dem Testfaellen:
+ - Anzahl in Projekten und Anwendungen
+ - letzte Durchfuehrungen (log)
+ - letzte Aenderungen (git log)
+ - info zu Begriff Testfall -> ucarmesin.de/joomla
+ - Suchfeld fuer speziellen Testfall -> Anzeige des Testfalls

+ + + + + + + +{% endblock %} diff --git a/webflask/templates/testsuite/overview.html b/webflask/templates/testsuite/overview.html new file mode 100644 index 0000000..5dffad5 --- /dev/null +++ b/webflask/templates/testsuite/overview.html @@ -0,0 +1,9 @@ +{% extends 'base.html' %} + +{% block header %} +

{% block title %}Overview{% endblock %}

+{% endblock %} + +{% block content %} +

Hier kommt Inhalt rein

+{% endblock %} diff --git a/webflask/testcase.py b/webflask/testcase.py new file mode 100644 index 0000000..d2c4948 --- /dev/null +++ b/webflask/testcase.py @@ -0,0 +1,41 @@ +# https://flask.palletsprojects.com/en/2.0.x/tutorial/views/ +# -------------------------------------------------------------- +import functools +import utils.config_tool + +from flask import ( + Blueprint, flash, g, redirect, render_template, request, session, url_for +) +from werkzeug.security import check_password_hash, generate_password_hash + +from webflask.db import get_db + +bp = Blueprint('testcase', __name__, url_prefix='/testcase') + +@bp.route('/selection', methods=('GET', 'POST')) +def selection(): + if request.method == 'POST': + error = None + + flash(error) + + return render_template('testcase/selection.html') + +@bp.route('/overview', methods=('GET', 'POST')) +def overview(): + """ + it shows an overview to testcase + :param: nothing + :return: rendered page with jsondata + * info with link to ucarmesin.de/joomla + * last git-updates (specifications) + * last executions (-> reports) with status running|to-check|with-faults|ok + * selection of conrete testcase by application, testsuite, project, regex?, full name + """ + if request.method == 'POST': + error = None + + flash(error) + + return render_template('testcase/overview.html') + diff --git a/webflask/testsuite.py b/webflask/testsuite.py new file mode 100644 index 0000000..ddd78b4 --- /dev/null +++ b/webflask/testsuite.py @@ -0,0 +1,31 @@ +# https://flask.palletsprojects.com/en/2.0.x/tutorial/views/ +# -------------------------------------------------------------- +import functools + +from flask import ( + Blueprint, flash, g, redirect, render_template, request, session, url_for +) +from werkzeug.security import check_password_hash, generate_password_hash + +from webflask.db import get_db + +bp = Blueprint('testsuite', __name__, url_prefix='/testsuite') + +@bp.route('/selection', methods=('GET', 'POST')) +def selection(): + if request.method == 'POST': + error = None + + flash(error) + + return render_template('testsuite/selection.html') + +@bp.route('/overview', methods=('GET', 'POST')) +def overview(): + if request.method == 'POST': + error = None + + flash(error) + + return render_template('testsuite/overview.html') +