Get the ball rolling

- Add templates
- Add static assets
- Add module setup
Raoul Snyman 2021-09-17 10:06:49 -07:00
parent 9e6cb91da9
commit 6b29e2a373
No known key found for this signature in database
GPG Key ID: F55BCED79626AE9C
11 changed files with 286 additions and 8 deletions

2
.gitignore vendored 100644
View File

@ -0,0 +1,2 @@
__pycache__
*.py[co]

4
README.rst 100644
View File

@ -0,0 +1,4 @@
StatusForce
===========
StatusForce is a simple, open source status page written in Python and Flask.

37
setup.cfg 100644
View File

@ -0,0 +1,37 @@
[metadata]
name = StatusForce
version = 0.0.1
author = Raoul Snyman
author_email = raoul@libertytechforce.com
description = Simple status page for system status
long_description = file:README.rst
long_description_content_type = text/x-rst
url = https://libertytechforce.com
license = MIT
classifiers =
Development Status :: 3 - Alpha
Intended Audience :: Developers
License :: OSI Approved :: MIT License
Operating System :: POSIX
Programming Language :: Python :: 3
Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10
Topic :: Utilities
keywords = website, status
[options]
py_modules = statusforce
python_requires = >=3.7
install_requires =
Flask
Flask-Admin
Flask-SQLAlchemy
Flask-Login
[bdist_wheel]
universal = 1
[flake8]
max-line-length = 120

6
setup.py 100644
View File

@ -0,0 +1,6 @@
"""
The statusforce package
"""
from setuptools import setup
setup()

View File

@ -1,17 +1,36 @@
from flask import Flask
from flask import Flask, render_template
from flask_admin import Admin
from flask_admin.contrib.sqla import ModelView
from statusforce.db import db
from statusforce.db import db, session
from statusforce.models import Service, Incident
# Set up Flask application
# Set up Flask application and initial config
app = Flask(__name__)
app.secret_key = b'GSADFGST#$%^$%&^2345234534576476'
app.config['FLASK_ADMIN_SWATCH'] = 'materia'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db'
# Set up database
db.init_app(app)
with app.app_context():
db.create_all()
# Set up admin interface
admin = Admin(app, name='StatusForce', template_mode='bootstrap4')
admin.add_view(ModelView(Service))
admin.add_view(ModelView(Incident))
admin.add_view(ModelView(Service, session))
admin.add_view(ModelView(Incident, session))
@app.route('/')
def index():
"""
Show the status page
"""
services = Service.query.all()
incidents = Incident.query.limit(10).all()
service_statuses = [service.status for service in services]
overall_status = 'operational' if all(status == 'operational' for status in service_statuses) else \
'offline' if all(status == 'offline' for status in service_statuses) else 'unclear'
return render_template('index.html', services=services, incidents=incidents, overall_status=overall_status)

View File

@ -3,11 +3,13 @@ from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
# Pull out some attributes to make life easier
session = db.session
Model = db.Model
Column = db.Column
ForeignKey = db.ForeignKey
Boolean = db.Boolean
DateTime = db.DateTime
Enum = db.Enum
Integer = db.Integer
String = db.String
relationship = db.relationship

View File

@ -2,18 +2,20 @@ from datetime import datetime
from sqlalchemy.sql.expression import func
from statusforce.db import Model, Column, ForeignKey, DateTime, Integer, String
from statusforce.db import Model, Column, ForeignKey, Boolean, DateTime, Enum, Integer, String, relationship, backref
class Service(Model):
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String, nullable=False)
status = Column(String, choices=['operational', 'unclear', 'offline'])
status = Column(Enum('operational', 'unclear', 'offline'), default='operational')
incidents = relationship('Incident', backref=backref('services', lazy=True))
class Incident(Model):
id = Column(Integer, primary_key=True, autoincrement=True)
service_id = Column(Integer, ForeignKey('service.id'))
title = Column(String, nullable=False)
is_resolved = Column(Boolean, default=False)
created = Column(DateTime, nullable=False, default=datetime.utcnow)
updated = Column(DateTime, unupdate=func.current_timestamp())
updated = Column(DateTime, onupdate=func.current_timestamp())

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,119 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="260mm"
height="260mm"
version="1.1"
viewBox="0 0 260 260"
id="svg855"
sodipodi:docname="ltf-logo.svg"
inkscape:export-filename="/home/raoul/Nextcloud/Documents/LibertyTechForce/ltf-logo.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
inkscape:version="1.0.2 (e86c870879, 2021-01-15)">
<defs
id="defs859" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1015"
id="namedview857"
showgrid="false"
inkscape:zoom="0.83140224"
inkscape:cx="491.33858"
inkscape:cy="491.33858"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg855" />
<metadata
id="metadata833">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
transform="translate(43.957 -17.442)"
id="g853">
<path
d="m86.043 22.059c-39.82-5.3e-5 -79.258 7.7669-116.1 22.866l-4.4511 1.8241 0.37669 4.7952c6.6963 85.322 48.803 163.96 116.1 216.82l4.0744 3.201 4.0744-3.201c67.302-52.869 109.41-131.5 116.11-216.82l0.37616-4.7952-4.4505-1.8236c-36.847-15.1-76.286-22.868-116.11-22.868z"
color="#000000"
color-rendering="auto"
dominant-baseline="auto"
fill="none"
image-rendering="auto"
shape-rendering="auto"
solid-color="#000000"
stop-color="#000000"
stroke="#00a9ff"
stroke-linecap="round"
stroke-width="9.2339"
style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;font-variation-settings:normal;inline-size:0;isolation:auto;mix-blend-mode:normal;shape-margin:0;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal"
id="path835" />
<path
d="m86.042 35.423c-36.485-5.3e-5 -72.627 6.8321-106.59 20.095 7.426 78.046 45.817 149.85 106.59 199.36 60.769-49.515 99.161-121.32 106.59-199.36-33.959-13.263-70.102-20.096-106.59-20.097z"
color="#000000"
color-rendering="auto"
dominant-baseline="auto"
fill="#00a9ff"
fill-rule="evenodd"
image-rendering="auto"
shape-rendering="auto"
solid-color="#000000"
stop-color="#000000"
style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-east-asian:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;font-variation-settings:normal;inline-size:0;isolation:auto;mix-blend-mode:normal;shape-margin:0;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal"
id="path837" />
<g
transform="matrix(.26382 0 0 .26382 -1336.7 -404.22)"
fill="#fff"
id="g851">
<path
d="m5352.5 1709.1s1.8115 11.734 1.7243 30.702c-0.09 19.529-2.5476 44.4-11.336 72.898-9.507 30.713-24.958 61.051-46.461 90.248-5.0966 6.9914-10.776 14.22-16.487 21.719-3.3072 4.3423-6.6851 8.8526-10.036 13.521-9.2086 12.678-18.137 26.635-25.058 41.958-7.6521 17.178-11.474 34.532-12.175 51.292-0.7637 18.134 2.0741 35.132 7.1133 50.426 6.0002 18.23 14.964 33.594 24.243 45.894 9.8042 13 19.931 22.51 27.18 28.626 7.3049 6.1634 11.869 9.1825 11.869 9.1825s-2.1535-4.2599-5.1401-11.919c-2.9974-7.6862-6.6513-18.653-8.9284-32.125-2.1545-12.754-2.9786-27.224-1.2734-42.978 1.4321-13.183 4.593-26.798 9.8393-40.4 1.5837-4.1002 3.3452-8.1707 5.278-12.199-0.066 8.8009 1.804 17.074 4.9134 24.585 4.0812 9.8702 10.178 18.189 16.49 24.848 6.6688 7.0385 13.557 12.188 18.487 15.499 4.9688 3.3369 8.073 4.9716 8.073 4.9716s-1.4649-2.3069-3.4964-6.4535c-2.0386-4.1616-4.5234-10.1-6.0723-17.393-1.4656-6.9054-2.0261-14.74-0.8662-23.269 0.9741-7.1374 3.1238-14.509 6.6922-21.873 3.2961-6.7927 7.7049-13.44 13.22-19.722 4.9702-5.7674 11.177-11.598 17.587-17.998 2.3746-2.3425 4.7684-4.7396 7.1344-7.219 4.0841-4.2799 8.1465-8.87 11.876-13.783 0.7474-0.9928 1.4695-1.9874 2.1672-2.9841 4.8101 10.333 11.033 20.599 18.854 30.556 5.0335 6.3636 10.363 12.257 15.653 17.726 3.0648 3.1687 6.143 6.2227 9.1896 9.2042 8.239 8.1514 16.144 15.547 22.695 22.964 15.3 16.97 25.1 36.162 29.978 54.892 4.6856 18.186 4.3259 34.074 2.5531 46.169-1.7328 11.823-4.3539 18.9-4.3539 18.9s6.7539-5.3156 15.259-16.217c8.5175-10.917 18.459-27.599 22.255-49.266 3.9891-22.479 0.9691-48.912-14.848-74.687-6.7764-10.884-14.884-20.641-23.058-29.438-2.9805-3.242-5.9635-6.3653-8.8761-9.3692-5.03-5.188-10.007-10.178-14.528-15.029-19.044-20.242-29.917-44.553-36.114-66.89-6.3714-24.38-7.141-41.046-6.0269-54.337 1.2586-15.015 8.7797-49.583 8.7797-49.583s-22.432 42.5-39.254 50.098c1.206-12.117 1.176-23.997 0.094-35.444-3.1047-33.196-14.642-59.919-25.291-78.338-10.556-18.259-19.52-27.968-19.52-27.968z"
id="path839" />
<g
fill-rule="evenodd"
id="g849">
<ellipse
cx="5390.9"
cy="2209.8"
rx="122.99"
ry="42.108"
id="ellipse841" />
<ellipse
cx="5390.9"
cy="2253.7"
rx="107.4"
ry="24.427"
id="ellipse843" />
<path
d="m5306.1 2195.9h169.48l-23.609 189.53h-122.26z"
id="path845" />
<ellipse
cx="5390.9"
cy="2385.2"
rx="69.792"
ry="15.873"
id="ellipse847" />
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.3 KiB

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,73 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>System Status</title>
<link href="{{url_for('static', filename='css/bootstrap.min.css')}}" rel="stylesheet">
<style>
.bd-placeholder-img {
font-size: 1.125rem;
text-anchor: middle;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
}
@media (min-width: 768px) {
.bd-placeholder-img-lg {
font-size: 3.5rem;
}
}
</style>
</head>
<body class="bg-light">
<div class="container">
<main>
<div class="py-5 text-center">
<img class="d-block mx-auto mb-4" src="{{url_for('static', filename='img/ltf-logo.svg')}}" alt="" width="72" height="57">
<h2>Server Status</h2>
<p class="lead">The current status of the Liberty Tech Force systems.</p>
</div>
{% if overall_status == 'operational' %}
<div class="p-3 mb-5 bg-success text-white" style="border-radius: 3px;">
All systems are operational
</div>
{% elif overall_status == 'unclear' %}
<div class="p-3 mb-5 bg-warning text-white" style="border-radius: 3px;">
Some systems are experiencing problems
</div>
{% elif overall_status == 'offline' %}
<div class="p-3 mb-5 bg-danger text-white" style="border-radius: 3px;">
<strong>There is a major disruption of services</strong>
</div>
{% endif %}
<ul class="list-group">
{% for service in services %}
{% if service.status == 'operational' %}
<li class="list-group-item d-flex justify-content-between align-items-center p-3">
{% elif service.status == 'unclear' %}
<li class="list-group-item d-flex justify-content-between align-items-center p-3 bg-warning" style="--bg-opacity: 0.2">
{% elif service.status == 'offline' %}
<li class="list-group-item d-flex justify-content-between align-items-center p-3 bg-danger text-white" style="--bg-opacity: 0.2">
{% endif %}
{{service.name}}
{% if service.status == 'operational' %}
<span class="p-2 bg-success border border-light rounded-circle">
{% elif service.status == 'unclear' %}
<span class="p-2 bg-warning border border-light rounded-circle">
{% elif service.status == 'offline' %}
<span class="p-2 bg-danger border border-light rounded-circle">
{% endif %}
<span class="visually-hidden">{{service.status}}</span>
</span>
</li>
{% endfor %}
</ul>
</main>
<footer class="my-5 pt-5 text-muted text-center text-small">
<p class="mb-1">&copy; 2021 Liberty Tech Force</p>
</footer>
</div>
<!-- script src="js/bootstrap.bundle.min.js"></script -->
</body>
</html>