Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrading to Flask 3.0 with minor styling changes #59

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 11 additions & 10 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
FROM python:3.7-alpine
FROM python:3.11.2-slim

ADD requirements.txt .
WORKDIR /app

RUN apk add python3-dev build-base linux-headers pcre-dev && pip install --no-cache-dir -r requirements.txt
COPY . /app

# adding application files
ADD . /webapp
RUN pip install --upgrade pip

# configure path /webapp to HOME-dir
ENV HOME /webapp
WORKDIR /webapp
RUN pip install --no-cache-dir -r requirements.txt

ENTRYPOINT ["uwsgi"]
CMD ["--http", "0.0.0.0:8080", "--wsgi-file", "wsgi.py", "--callable", "app", "--processes", "1", "--threads", "8"]
EXPOSE 8080

ENV FLASK_APP=app
ENV FLASK_ENV=production

CMD ["gunicorn", "--workers", "1", "--timeout", "5000", "--preload", "--bind", "0.0.0.0:8080", "wsgi:app"]
11 changes: 6 additions & 5 deletions app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,22 @@
from config import config
from flask_moment import Moment


moment = Moment()


def create_app(config_name):
app = Flask(__name__)
app.config.from_object(config[config_name])

config[config_name].init_app(app)

config[config_name].init_app(app)
moment.init_app(app)

from app.ui import ui as ui_blueprint
app.register_blueprint(ui_blueprint)
from app.ui import views as ui_views
app.register_blueprint(ui_views.bp)

from app.api import api as api_blueprint
app.register_blueprint(api_blueprint, url_prefix='/api')
from app.api import endpoints as api_endpoints
app.register_blueprint(api_endpoints.bp)

return app
5 changes: 0 additions & 5 deletions app/api/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +0,0 @@
from flask import Blueprint

api = Blueprint('api', __name__)

from . import endpoints
63 changes: 54 additions & 9 deletions app/api/endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
import io
import os
import flask
import platform
import docker

from app.api import api
bp = flask.Blueprint('api', __name__, url_prefix='/api')


@api.route('/config/<name>', methods=['GET'])
@bp.route('/config/<name>', methods=['GET'])
def get_config(name: str):
"""
Reads the file with the corresponding name that was passed.
Expand All @@ -25,7 +27,7 @@ def get_config(name: str):
return flask.render_template('config.html', name=name, file=_file), 200


@api.route('/config/<name>', methods=['POST'])
@bp.route('/config/<name>', methods=['POST'])
def post_config(name: str):
"""
Accepts the customized configuration and saves it in the configuration file with the supplied name.
Expand All @@ -45,7 +47,7 @@ def post_config(name: str):
return flask.make_response({'success': True}), 200


@api.route('/domains', methods=['GET'])
@bp.route('/domains', methods=['GET'])
def get_domains():
"""
Reads all files from the configuration file directory and checks the state of the site configuration.
Expand Down Expand Up @@ -83,7 +85,7 @@ def get_domains():
return flask.render_template('domains.html', sites_available=sites_available, sites_enabled=sites_enabled), 200


@api.route('/domain/<name>', methods=['GET'])
@bp.route('/domain/<name>', methods=['GET'])
def get_domain(name: str):
"""
Takes the name of the domain configuration file and
Expand Down Expand Up @@ -116,7 +118,7 @@ def get_domain(name: str):
return flask.render_template('domain.html', name=name, file=_file, enabled=enabled), 200


@api.route('/domain/<name>', methods=['POST'])
@bp.route('/domain/<name>', methods=['POST'])
def post_domain(name: str):
"""
Creates the configuration file of the domain.
Expand All @@ -141,7 +143,7 @@ def post_domain(name: str):
return response


@api.route('/domain/<name>', methods=['DELETE'])
@bp.route('/domain/<name>', methods=['DELETE'])
def delete_domain(name: str):
"""
Deletes the configuration file of the corresponding domain.
Expand All @@ -168,7 +170,7 @@ def delete_domain(name: str):
return flask.jsonify({'success': False}), 400


@api.route('/domain/<name>', methods=['PUT'])
@bp.route('/domain/<name>', methods=['PUT'])
def put_domain(name: str):
"""
Updates the configuration file with the corresponding domain name.
Expand All @@ -191,7 +193,7 @@ def put_domain(name: str):
return flask.make_response({'success': True}), 200


@api.route('/domain/<name>/enable', methods=['POST'])
@bp.route('/domain/<name>/enable', methods=['POST'])
def enable_domain(name: str):
"""
Activates the domain in Nginx so that the configuration is applied.
Expand All @@ -215,3 +217,46 @@ def enable_domain(name: str):
os.rename(os.path.join(config_path, _), os.path.join(config_path, _ + '.disabled'))

return flask.make_response({'success': True}), 200


def is_rhel():
try:
with open('/etc/os-release') as f:
os_release = f.read()
return 'rhel' in os_release.lower()
except FileNotFoundError:
return False


USE_SUDO_PODMAN = is_rhel()


def get_docker_client():
print("Platform: ", platform.system())
if USE_SUDO_PODMAN:
base_url = f'unix:///run/user/{os.getuid()}/podman/podman.sock'
elif platform.system() == "Windows":
base_url = 'npipe:////./pipe/docker_engine'
else:
base_url = 'unix://var/run/docker.sock'
return docker.DockerClient(base_url=base_url)


client = get_docker_client()


@bp.route('/restart-nginx', methods=['POST'])
def restart_nginx():
try:
container = client.containers.get('nginx')
container.restart()
except Exception as e:
return flask.jsonify({"message": f"Error: {e}"}), 500
return flask.jsonify({"message": "Nginx container restarted"}), 200


@bp.route('/status-nginx', methods=['GET'])
def status_nginx():
container = client.containers.get('nginx')
status = container.status
return flask.jsonify({"container": "nginx", "status": status}), 200
93 changes: 84 additions & 9 deletions app/static/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,92 @@ textarea {
#main-container {
margin-top: 5em;
}

#domain {
display: none;
}

@media only screen and (max-width: 666px) {
[class*="mobile hidden"],
[class*="tablet only"]:not(.mobile),
[class*="computer only"]:not(.mobile),
[class*="large monitor only"]:not(.mobile),
[class*="widescreen monitor only"]:not(.mobile),
[class*="or lower hidden"] {
display: none !important;
}
}
[class*="mobile hidden"],
[class*="tablet only"]:not(.mobile),
[class*="computer only"]:not(.mobile),
[class*="large monitor only"]:not(.mobile),
[class*="widescreen monitor only"]:not(.mobile),
[class*="or lower hidden"] {
display: none !important;
}
}

.status-circle {
display: inline-block;
width: 10px;
height: 10px;
border-radius: 50%;
margin-right: 5px;
}

.status-running {
background-color: limegreen;
}

.status-exited {
background-color: red;
}

.status-loading {
background-color: orange;
}

.status-paused {
background-color: grey;
}

.status-black {
background-color: black;
}

.restart-button {
margin-left: 10px;
padding: 5px 10px;
background-color: #007bff;
color: white;
border: none;
border-radius: 3px;
cursor: pointer;
}

.restart-button:hover {
background-color: #0056b3;
}

@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}

.custom-spinner {
display: inline-block;
width: 2rem;
height: 2rem;
vertical-align: text-bottom;
border: 0.25em solid currentColor;
border-right-color: transparent;
border-radius: 50%;
animation: spin 0.75s linear infinite;
}

.custom-spinner-sm {
width: 1rem;
height: 1rem;
border-width: 0.2em;
}

.custom-spinner-lg {
width: 3rem;
height: 3rem;
border-width: 0.3em;
}
63 changes: 63 additions & 0 deletions app/static/nginxController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
document.addEventListener('DOMContentLoaded', function () {
setNginxStatusCircle();
});

function setNginxStatusCircle() {
const nginxStatusCircle = document.getElementById('nginx-status-circle');

fetch('/api/status-nginx')
.then(response => response.json())
.then(data => {
console.log(data);

const containerStatus = data.status;
console.log("Container status: " + containerStatus);
nginxStatusCircle.className = 'status-circle status-' + containerStatus;
return containerStatus;
});
}

let restartProcessComplete = true;

function restartStatusInterval() {
const nginxStatusCircle = document.getElementById('nginx-status-circle');
nginxStatusCircle.className = 'status-circle status-loading';

const intervalId = setInterval(() => {
if (restartProcessComplete) {
clearInterval(intervalId);
setNginxStatusCircle();
} else {
if (nginxStatusCircle.className === 'status-circle status-loading') {
nginxStatusCircle.className = 'status-circle status-black';
} else {
nginxStatusCircle.className = 'status-circle status-loading';
}
}
}, 100);
}


function restartNginx() {
restartProcessComplete = false;
restartStatusInterval();

const restartBtn = document.getElementById('nginx-restart-btn');
restartBtn.disabled = true;

const restartSpinner = document.getElementById('nginx-restart-btn-spinner');
restartSpinner.className = 'custom-spinner custom-spinner-sm';


fetch('/api/restart-nginx', {method: "POST"})
.then(response => response.json())
.then(data => {
console.log(data);
})
.finally(() => {
setNginxStatusCircle();
restartProcessComplete = true;
restartSpinner.className = '';
restartBtn.disabled = false;
});
}
10 changes: 10 additions & 0 deletions app/templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
{{ moment.include_moment() }}
<script src="{{ url_for('static', filename='semantic.min.js') }}"></script>
<script src="{{ url_for('static', filename='custom.min.js') }}"></script>
<script src="{{ url_for('static', filename='nginxController.js') }}"></script>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='custom.css') }}">
</head>
<body>
Expand Down Expand Up @@ -40,6 +41,15 @@
Domains
</a>

<a class="item" id="nginx-status">
<span class="status-circle status-loading" id="nginx-status-circle"></span>
Nginx Status
<button class="restart-button" style="margin-left: 10px" onclick="restartNginx()" id="nginx-restart-btn" type="button">
<span class="" aria-hidden="true" id="nginx-restart-btn-spinner"></span>
<span role="status">Restart</span>
</button>
</a>

<div class="mobile hidden right item">
<div class="ui action input">
<input type="text" placeholder="example.com" id="add_domain">
Expand Down
5 changes: 0 additions & 5 deletions app/ui/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +0,0 @@
from flask import Blueprint

ui = Blueprint('ui', __name__)

from . import views
Loading