Compare commits
3 Commits
master
...
f57e28f50b
Author | SHA1 | Date |
---|---|---|
![]() |
f57e28f50b | |
![]() |
7a826cc4bc | |
![]() |
6b29e2a373 |
|
@ -0,0 +1,3 @@
|
|||
__pycache__
|
||||
*.py[co]
|
||||
*.sqlite
|
|
@ -0,0 +1,4 @@
|
|||
StatusForce
|
||||
===========
|
||||
|
||||
StatusForce is a simple, open source status page written in Python and Flask.
|
|
@ -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
|
|
@ -0,0 +1,6 @@
|
|||
"""
|
||||
The statusforce package
|
||||
"""
|
||||
from setuptools import setup
|
||||
|
||||
setup()
|
|
@ -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'
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///statusforce.sqlite'
|
||||
|
||||
# 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.order_by(Incident.created.desc()).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)
|
||||
|
|
|
@ -3,12 +3,15 @@ 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
|
||||
Text = db.Text
|
||||
relationship = db.relationship
|
||||
backref = db.backref
|
||||
|
|
|
@ -2,18 +2,28 @@ 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, Text, \
|
||||
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('service', lazy=True))
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class Incident(Model):
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
service_id = Column(Integer, ForeignKey('service.id'))
|
||||
title = Column(String, nullable=False)
|
||||
description = Column(Text)
|
||||
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())
|
||||
|
||||
def __str__(self):
|
||||
return '{} ({})'.format(self.title, self.service.name)
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
|
@ -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
|
@ -0,0 +1,119 @@
|
|||
<!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">
|
||||
<link href="{{url_for('static', filename='css/bootstrap-icons.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 px-5">
|
||||
<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;">
|
||||
<i class="bi-check-circle-fill" style="margin-right: 0.5rem;"></i> All systems are operational
|
||||
</div>
|
||||
{% elif overall_status == 'unclear' %}
|
||||
<div class="p-3 mb-5 bg-warning" style="border-radius: 3px;">
|
||||
<i class="bi-question-circle-fill" style="margin-right: 0.5rem;"></i> Some systems are experiencing problems
|
||||
</div>
|
||||
{% elif overall_status == 'offline' %}
|
||||
<div class="p-3 mb-5 bg-danger text-white" style="border-radius: 3px;">
|
||||
<i class="bi-exclamation-circle-fill" style="margin-right: 0.5rem;"></i> <strong>There is a major disruption of services</strong>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if services | length > 0 %}
|
||||
<h2 class="mt-5 mb-3">Services</h2>
|
||||
<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="--bs-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" style="--bs-bg-opacity: 0.2">
|
||||
{% endif %}
|
||||
{{service.name}}
|
||||
{% if service.status == 'operational' %}
|
||||
<span class="p-2 bg-success rounded-circle">
|
||||
{% elif service.status == 'unclear' %}
|
||||
<span class="p-2 bg-warning rounded-circle">
|
||||
{% elif service.status == 'offline' %}
|
||||
<span class="p-2 bg-danger rounded-circle">
|
||||
{% endif %}
|
||||
<span class="visually-hidden">{{service.status}}</span>
|
||||
</span>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% if incidents | length > 0 %}
|
||||
<h2 class="mt-5 mb-3">Incidents</h2>
|
||||
{% endif %}
|
||||
{% for incident in incidents %}
|
||||
<div class="row">
|
||||
<div class="col-auto text-center flex-column d-none d-sm-flex">
|
||||
<div class="row h-50">
|
||||
{% if loop.first %}
|
||||
<div class="col"> </div>
|
||||
<div class="col"> </div>
|
||||
{% else %}
|
||||
<div class="col border-end border-2"> </div>
|
||||
<div class="col border-start border-2"> </div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<!-- h5 class="m-2"><span class="rounded-circle bg-white border px-3 py-1"></span></h5 -->
|
||||
{% if incident.is_resolved %}
|
||||
<h5 class="m-2"><span class="bi-check-circle-fill text-success" style="font-size: 200%"></span></h5>
|
||||
{% else %}
|
||||
<h5 class="m-2"><span class="bi-exclamation-circle-fill text-danger" style="font-size: 200%"></span></h5>
|
||||
{% endif %}
|
||||
<div class="row h-50">
|
||||
{% if loop.last %}
|
||||
<div class="col"> </div>
|
||||
<div class="col"> </div>
|
||||
{% else %}
|
||||
<div class="col border-end border-2"> </div>
|
||||
<div class="col border-start border-2"> </div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col py-2">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="float-end">{{incident.created.strftime('%H:%M%z - %a %d %B, %Y')}}</div>
|
||||
<h4 class="card-title text-muted">{{incident.title}} - {{incident.service.name}}</h4>
|
||||
<p class="card-text text-muted">{{incident.description}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</main>
|
||||
<footer class="my-5 pt-5 text-muted text-center text-small">
|
||||
<p class="mb-1">© 2021 <a href="https://libertytechforce.com">Liberty Tech Force</a> | Powered by <a href="https://git.libertytechforce.com/libertytechforce/statusforce">StatusForce</a></p>
|
||||
</footer>
|
||||
</div>
|
||||
<!-- script src="js/bootstrap.bundle.min.js"></script -->
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue