-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
✨ Add initial grafana dashboard application
- Loading branch information
1 parent
955de48
commit 4aad5b5
Showing
31 changed files
with
2,651 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
Dockerfile | ||
.git/ | ||
.github/ | ||
|
||
*.py[cod] | ||
|
||
# C extensions | ||
*.so | ||
|
||
# Packages | ||
*.egg | ||
*.egg-info | ||
dist | ||
build | ||
eggs | ||
parts | ||
var | ||
sdist | ||
develop-eggs | ||
.installed.cfg | ||
lib | ||
lib64 | ||
|
||
# Installer logs | ||
pip-log.txt | ||
|
||
# Unit test / coverage reports | ||
.coverage | ||
.tox | ||
nosetests.xml | ||
|
||
*.log | ||
|
||
# Translations | ||
*.mo | ||
|
||
# Mr Developer | ||
.mr.developer.cfg | ||
.project | ||
.pydevproject | ||
|
||
# Complexity | ||
output/*.html | ||
output/*/index.html | ||
|
||
# Sphinx | ||
docs/_build | ||
|
||
.webassets-cache | ||
|
||
# Virtualenvs | ||
env | ||
env* | ||
venv | ||
|
||
# intellij | ||
.idea/ | ||
*.ipr | ||
*.iml | ||
*.iws | ||
|
||
.DS_Store | ||
|
||
# node | ||
node_modules/ | ||
|
||
.sass-cache/ | ||
|
||
docs/ | ||
README.md |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
FROM python:3.12.0-slim | ||
|
||
RUN groupadd -r user && useradd -r -g user 1051 | ||
|
||
WORKDIR /home/operations-engineering-kubera | ||
|
||
COPY Pipfile Pipfile | ||
COPY Pipfile.lock Pipfile.lock | ||
COPY app app | ||
COPY example-data data | ||
COPY data/production production | ||
|
||
RUN pip3 install --no-cache-dir pipenv | ||
RUN pipenv install --system --deploy --ignore-pipfile | ||
|
||
ENV PYTHONDONTWRITEBYTECODE 1 | ||
ENV PYTHONUNBUFFERED 1 | ||
|
||
EXPOSE 4567 | ||
|
||
USER 1051 | ||
|
||
ENTRYPOINT ["gunicorn", "--bind=0.0.0.0:4567", "app.run:app()"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
[[source]] | ||
url = "https://pypi.org/simple" | ||
verify_ssl = true | ||
name = "pypi" | ||
|
||
[packages] | ||
authlib = "==1.3.1" | ||
dash_auth = "==2.3.0" | ||
flask = "==3.0.0" | ||
gunicorn = "==21.2.0" | ||
psycopg2-binary = "2.9.9" | ||
plotly = "==5.21.0" | ||
dash = "==2.16.1" | ||
pandas = "==2.2.2" | ||
statsmodels = "==0.14.1" | ||
|
||
[requires] | ||
python_version = "3.12" |
Large diffs are not rendered by default.
Oops, something went wrong.
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
import logging | ||
|
||
from dash import Dash, dcc, html | ||
from dash_auth import OIDCAuth, add_public_routes | ||
from flask import Flask | ||
|
||
from app.config.app_config import app_config | ||
from app.config.logging_config import configure_logging | ||
from app.config.routes_config import configure_routes | ||
from app.services.database_service import DatabaseService | ||
from app.services.figure_service import FigureService | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
def create_app() -> Flask: | ||
configure_logging(app_config.logging_level) | ||
|
||
logger.info("Starting app...") | ||
|
||
server = Flask(__name__) | ||
|
||
server.database_service = DatabaseService() | ||
server.figure_service = FigureService(server.database_service) | ||
|
||
configure_routes(server) | ||
|
||
logger.info("Populating stub data...") | ||
server.database_service.create_indicators_table() | ||
server.database_service.clean_stubbed_indicators_table() | ||
|
||
app = Dash(__name__, server=server, url_base_pathname="/dashboard/") | ||
app.title = "⚙️ SLO dashboard" | ||
app.layout = create_dashboard(server.figure_service) | ||
|
||
if app_config.auth_enabled: | ||
auth = OIDCAuth( | ||
app, | ||
secret_key=app_config.flask.app_secret_key, | ||
force_https_callback=True, | ||
secure_session=True, | ||
) | ||
add_public_routes(app, routes=["/api/indicator/add"]) | ||
auth.register_provider( | ||
"idp", | ||
token_endpoint_auth_method="client_secret_post", | ||
client_id=app_config.auth0.client_id, | ||
client_secret=app_config.auth0.client_secret, | ||
server_metadata_url=f"https://{app_config.auth0.domain}/.well-known/openid-configuration", | ||
) | ||
logger.info("Running app...") | ||
|
||
return app.server | ||
|
||
|
||
def create_dashboard(figure_service: FigureService): | ||
def dashboard(): | ||
return html.Div( | ||
children=[ | ||
html.H1("🤩 Live Data 🤩"), | ||
dcc.Graph( | ||
figure=figure_service.get_number_of_repositories_with_standards_label_dashboard(), | ||
style={ | ||
"width": "100%", | ||
"height": "500px", | ||
"display": "inline-block", | ||
}, | ||
), | ||
dcc.Graph( | ||
figure=figure_service.get_support_stats_year_to_date(), | ||
style={ | ||
"width": "100%", | ||
"height": "500px", | ||
"display": "inline-block", | ||
}, | ||
), | ||
dcc.Graph( | ||
figure=figure_service.get_support_stats_current_month(), | ||
style={ | ||
"width": "100%", | ||
"height": "500px", | ||
"display": "inline-block", | ||
}, | ||
), | ||
html.H2("Sentry Quota"), | ||
dcc.Graph( | ||
figure=figure_service.get_sentry_transactions_usage(), | ||
style={ | ||
"width": "100%", | ||
"height": "500px", | ||
"display": "inline-block", | ||
}, | ||
), | ||
dcc.Graph( | ||
figure=figure_service.get_sentry_errors_usage(), | ||
style={ | ||
"width": "50%", | ||
"height": "500px", | ||
"display": "inline-block", | ||
}, | ||
), | ||
dcc.Graph( | ||
figure=figure_service.get_sentry_replays_usage(), | ||
style={ | ||
"width": "50%", | ||
"height": "500px", | ||
"display": "inline-block", | ||
}, | ||
), | ||
html.H2("Github Actions Quota"), | ||
dcc.Graph( | ||
figure=figure_service.get_github_actions_quota_usage_cumulative()[ | ||
0 | ||
], | ||
style={ | ||
"width": "100%", | ||
"height": "500px", | ||
"display": "inline-block", | ||
}, | ||
), | ||
dcc.Graph( | ||
figure=figure_service.get_github_actions_quota_usage_cumulative()[ | ||
1 | ||
], | ||
style={ | ||
"width": "100%", | ||
"height": "500px", | ||
"display": "inline-block", | ||
}, | ||
), | ||
html.H1("🙈 Stub Data 🙈"), | ||
dcc.Graph( | ||
figure=figure_service.get_stubbed_number_of_repositories_with_standards_label_dashboard(), | ||
style={ | ||
"width": "33%", | ||
"height": "500px", | ||
"display": "inline-block", | ||
}, | ||
), | ||
dcc.Graph( | ||
figure=figure_service.get_stubbed_number_of_repositories_archived_by_automation(), | ||
style={ | ||
"width": "33%", | ||
"height": "500px", | ||
"display": "inline-block", | ||
}, | ||
), | ||
dcc.Graph( | ||
figure=figure_service.get_stubbed_sentry_transactions_used(), | ||
style={ | ||
"width": "33%", | ||
"height": "500px", | ||
"display": "inline-block", | ||
}, | ||
), | ||
dcc.Graph( | ||
figure=figure_service.get_support_stats(), | ||
style={ | ||
"width": "100%", | ||
"height": "500px", | ||
"display": "inline-block", | ||
}, | ||
), | ||
], | ||
style={"padding": "0px", "margin": "0px", "background-color": "black"}, | ||
) | ||
|
||
return dashboard |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
|
||
body { | ||
margin: 0px; | ||
font-family: monospace; | ||
color: white; | ||
} | ||
|
||
h1 { | ||
padding-top: 20px; | ||
margin-top: 0px; | ||
} |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import os | ||
from types import SimpleNamespace | ||
|
||
|
||
def __get_env_var(name: str) -> str | None: | ||
return os.getenv(name) | ||
|
||
|
||
def __get_env_var_as_boolean(name: str, default: bool) -> bool | None: | ||
value = __get_env_var(name) | ||
|
||
if value is None: | ||
return default | ||
|
||
if value.lower() == "true": | ||
return True | ||
|
||
if value.lower() == "false": | ||
return False | ||
|
||
return default | ||
|
||
|
||
app_config = SimpleNamespace( | ||
api_key=__get_env_var("API_KEY"), | ||
auth_enabled=__get_env_var_as_boolean("AUTH_ENABLED", True), | ||
auth0=SimpleNamespace( | ||
domain=__get_env_var("AUTH0_DOMAIN"), | ||
client_id=__get_env_var("AUTH0_CLIENT_ID"), | ||
client_secret=__get_env_var("AUTH0_CLIENT_SECRET"), | ||
), | ||
flask=SimpleNamespace( | ||
app_secret_key=__get_env_var("APP_SECRET_KEY"), | ||
), | ||
logging_level=__get_env_var("LOGGING_LEVEL"), | ||
postgres=SimpleNamespace( | ||
user=__get_env_var("POSTGRES_USER"), | ||
password=__get_env_var("POSTGRES_PASSWORD"), | ||
db=__get_env_var("POSTGRES_DB"), | ||
host=__get_env_var("POSTGRES_HOST"), | ||
port=__get_env_var("POSTGRES_PORT"), | ||
), | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import logging | ||
|
||
|
||
def configure_logging(logging_level: str) -> None: | ||
logging.basicConfig( | ||
format="{asctime:s} | {levelname:>8s} | {filename:s}:{lineno:d} | {message:s}", | ||
datefmt="%Y-%m-%dT%H:%M:%S", | ||
style="{", | ||
level=logging_level.upper() if logging_level else "INFO", | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
from flask import Flask | ||
|
||
from app.routes.api_route import api_route | ||
from app.routes.index_route import index_route | ||
|
||
|
||
def configure_routes(app: Flask) -> None: | ||
app.register_blueprint(api_route, url_prefix="/api") | ||
app.register_blueprint(index_route, url_prefix="/") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import logging | ||
from typing import Callable | ||
|
||
from flask import Blueprint, current_app, request | ||
|
||
from app.config.app_config import app_config | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
api_route = Blueprint("api_routes", __name__) | ||
|
||
|
||
def requires_api_key(func: Callable): | ||
def decorator(*args, **kwargs): | ||
if "X-API-KEY" not in request.headers or request.headers.get("X-API-KEY") != app_config.api_key: | ||
return "", 403 | ||
return func(*args, **kwargs) | ||
|
||
return decorator | ||
|
||
|
||
@api_route.route("/indicator/add", methods=["POST"]) | ||
@requires_api_key | ||
def add_indicator(): | ||
indicator = request.get_json().get("indicator") | ||
count = request.get_json().get("count") | ||
current_app.database_service.add_indicator(indicator, count) | ||
return ("", 204) |
Oops, something went wrong.