Skip to content
This repository was archived by the owner on Nov 14, 2024. It is now read-only.
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: ClubNix/nginx-rp-manager
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v1.0.0
Choose a base ref
...
head repository: ClubNix/nginx-rp-manager
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: master
Choose a head ref

Commits on Feb 28, 2024

  1. Fix issue in CSP format

    Isnubi committed Feb 28, 2024
    Copy the full SHA
    b91a7f0 View commit details
  2. Disable debug in app.run

    Isnubi committed Feb 28, 2024
    Copy the full SHA
    f359fd8 View commit details
  3. Copy the full SHA
    6617bb7 View commit details
  4. Add the render of the conf_list after a deletion in order to don't pr…

    …int the deleted service
    Isnubi committed Feb 28, 2024
    Copy the full SHA
    8d2f8b7 View commit details
  5. Copy the full SHA
    df29cda View commit details
  6. Remove useless css classes

    Isnubi committed Feb 28, 2024
    Copy the full SHA
    2fb497f View commit details

Commits on Feb 29, 2024

  1. Change logs retrieving (download the file instead of printing the 50 …

    …latest in non realtime)
    Isnubi committed Feb 29, 2024
    Copy the full SHA
    d9f4142 View commit details
  2. Sort the conf_list

    Isnubi committed Feb 29, 2024
    Copy the full SHA
    267470f View commit details

Commits on Mar 4, 2024

  1. Add typing

    Isnubi committed Mar 4, 2024
    Copy the full SHA
    34e4e3d View commit details

Commits on Mar 5, 2024

  1. Handle text given certificate

    Update create form to be more pretty
    Isnubi committed Mar 5, 2024
    Copy the full SHA
    848d946 View commit details
  2. Fix issue with favicon

    Isnubi committed Mar 5, 2024
    Copy the full SHA
    eb0bec2 View commit details
  3. Fix align issue

    Isnubi committed Mar 5, 2024
    Copy the full SHA
    bd07c40 View commit details
  4. Add printing of certificate information

    Edit "edit a service" like create
    Isnubi committed Mar 5, 2024
    Copy the full SHA
    c1c327f View commit details

Commits on Mar 29, 2024

  1. Copy the full SHA
    161d798 View commit details

Commits on Apr 9, 2024

  1. Add LICENSE

    Isnubi committed Apr 9, 2024
    Copy the full SHA
    60fe3f0 View commit details
  2. Copy the full SHA
    9ee5c7a View commit details
  3. Copy the full SHA
    cd64410 View commit details
  4. Adding logging in web-manager

    Isnubi committed Apr 9, 2024
    Copy the full SHA
    154ddbe View commit details

Commits on Sep 14, 2024

  1. Copy the full SHA
    4a3fc72 View commit details
  2. Copy the full SHA
    873aa43 View commit details
  3. Update footer

    Isnubi committed Sep 14, 2024
    Copy the full SHA
    ad8d853 View commit details
  4. Update reload_nginx function to add a configuration check in order to…

    … detect error in the configuration
    
    Add messages return after creating, modifying or deleting configuration
    Isnubi committed Sep 14, 2024
    Copy the full SHA
    befeb31 View commit details
  5. Copy the full SHA
    53567eb View commit details
  6. Copy the full SHA
    544fe4e View commit details

Commits on Nov 14, 2024

  1. Copy the full SHA
    499d05a View commit details
674 changes: 674 additions & 0 deletions docker/LICENSE

Large diffs are not rendered by default.

25 changes: 12 additions & 13 deletions docker/docker-compose.yml → docker/compose.yml
Original file line number Diff line number Diff line change
@@ -1,21 +1,12 @@
version: '3.8'

services:
reverse-proxy:
container_name: reverse-proxy
build:
context: ./reverse-proxy
args:
- SSL_COUNTRY="US"
- SSL_STATE="California"
- SSL_CITY="San Francisco"
- SSL_ORGANIZATION_GLOBAL="My Organization"
- SSL_ORGANIZATION_UNIT="My Department"
- SSL_DAYS=3650
dockerfile: Dockerfile
ports:
- "80:80"
- "443:443"
- 80:80
- 443:443
volumes:
- logs:/var/log/nginx
- conf:/etc/nginx/conf.d
@@ -28,17 +19,25 @@ services:
build:
context: ./web-manager
dockerfile: Dockerfile
environment:
- SSL_COUNTRY=FR
- SSL_STATE=France
- SSL_CITY=Noisy-le-Grand
- SSL_ORGANIZATION_GLOBAL=ESIEE Paris
- SSL_ORGANIZATION_UNIT=Club*Nix
- SSL_DAYS=365
ports:
- "5000:5000"
- 5000:5000
volumes:
- logs:/app/nginx/logs
- conf:/app/nginx/conf.d
- certs:/app/nginx/certs
- scripts:/app/scripts
- ./web-manager/logs:/app/logs
restart: unless-stopped

volumes:
logs:
conf:
certs:
scripts:
scripts:
24 changes: 6 additions & 18 deletions docker/reverse-proxy/Dockerfile
Original file line number Diff line number Diff line change
@@ -6,13 +6,6 @@ ARG NGINX_DIR='/etc/nginx'
ARG NGINX_CONF_DIR='/etc/nginx/conf.d'
ARG NGINX_LOG_DIR='/var/log/nginx'
ARG NGINX_SSL_DIR='/etc/nginx/certs'
# SSL Variables
ARG SSL_COUNTRY='US'
ARG SSL_STATE='California'
ARG SSL_CITY='San Francisco'
ARG SSL_ORGANIZATION_GLOBAL='My Organization'
ARG SSL_ORGANIZATION_UNIT='My Department'
ARG SSL_DAYS='3650'

# Install nginx and other packages
RUN apk update && \
@@ -30,30 +23,25 @@ RUN mkdir -p /opt && \
RUN mv $NGINX_DIR/nginx.conf $NGINX_DIR/nginx.conf.bak

COPY ./conf/nginx.conf $NGINX_DIR/nginx.conf
COPY ./conf/ssl.conf $NGINX_SSL_DIR/ssl.conf
COPY ./conf/manager.sh /opt/manager.sh
COPY ./conf/watch_reload_nginx.sh /opt/scripts/watch_reload_nginx.sh
COPY ./conf/watch_conf_check.sh /opt/scripts/watch_conf_check.sh

# Configure nginx
RUN sed -i "s|\$NGINX_DIR|$NGINX_DIR|g" $NGINX_DIR/nginx.conf && \
sed -i "s|\$NGINX_CONF_DIR|$NGINX_CONF_DIR|g" $NGINX_DIR/nginx.conf && \
sed -i "s|\$NGINX_LOG_DIR|$NGINX_LOG_DIR|g" $NGINX_DIR/nginx.conf

# Configure ssl
RUN sed -i "s|\$SSL_COUNTRY|${SSL_COUNTRY//\"}|g" $NGINX_SSL_DIR/ssl.conf && \
sed -i "s|\$SSL_STATE|${SSL_STATE//\"}|g" $NGINX_SSL_DIR/ssl.conf && \
sed -i "s|\$SSL_CITY|${SSL_CITY//\"}|g" $NGINX_SSL_DIR/ssl.conf && \
sed -i "s|\$SSL_ORGANIZATION_GLOBAL|${SSL_ORGANIZATION_GLOBAL//\"}|g" $NGINX_SSL_DIR/ssl.conf && \
sed -i "s|\$SSL_ORGANIZATION_UNIT|${SSL_ORGANIZATION_UNIT//\"}|g" $NGINX_SSL_DIR/ssl.conf && \
sed -i "s|\$SSL_DAYS|${SSL_DAYS//\"}|g" $NGINX_SSL_DIR/ssl.conf

# Get manager script
RUN sed -i "s|\$NGINX_CONF_DIR_REPLACE|$NGINX_CONF_DIR|g" /opt/manager.sh && \
sed -i "s|\$NGINX_SSL_DIR_REPLACE|$NGINX_SSL_DIR|g" /opt/manager.sh && \
sed -i "s|\$NGINX_LOG_DIR_REPLACE|$NGINX_LOG_DIR|g" /opt/manager.sh && \
chmod +x /opt/manager.sh && \
chmod +x /opt/scripts/watch_reload_nginx.sh && \
touch /opt/scripts/reload_nginx
chmod +x /opt/scripts/watch_conf_check.sh && \
touch /opt/scripts/reload_nginx && \
touch /opt/scripts/check_conf && \
touch /opt/scripts/check_conf_status

# Set working directory
WORKDIR /opt
@@ -65,4 +53,4 @@ HEALTHCHECK --interval=30s --timeout=3s CMD curl --fail http://localhost/health.
EXPOSE 80 443

# Run nginx
CMD ["/bin/bash", "-c", "/opt/scripts/watch_reload_nginx.sh & nginx -g 'daemon off;'"]
CMD ["/bin/bash", "-c", "/opt/scripts/watch_reload_nginx.sh & /opt/scripts/watch_conf_check.sh & nginx -g 'daemon off;'"]
10 changes: 10 additions & 0 deletions docker/reverse-proxy/conf/watch_conf_check.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/bin/bash

while inotifywait -e modify /opt/scripts/check_conf; do
out=$(nginx -t 2>&1)
if ! echo "$out" | grep -q "successful"; then
echo -e "1\n$out" > /opt/scripts/check_conf_status
else
echo -e "0" > /opt/scripts/check_conf_status
fi
done
3 changes: 2 additions & 1 deletion docker/web-manager/Dockerfile
Original file line number Diff line number Diff line change
@@ -7,4 +7,5 @@ COPY ./ /app

RUN pip install --no-cache-dir -r requirements.txt

CMD ["python", "app.py"]
CMD ["gunicorn", "-b", "0.0.0.0:5000", "app:app"]

2 changes: 1 addition & 1 deletion docker/web-manager/app.py
Original file line number Diff line number Diff line change
@@ -11,4 +11,4 @@ def index():


if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=5000)
app.run(debug=False, host='0.0.0.0', port=5000)
174 changes: 126 additions & 48 deletions docker/web-manager/blueprints/manager.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,44 @@
from flask import Blueprint, render_template, request, send_from_directory
import flask
from flask import Blueprint, render_template, request, send_from_directory, send_file
import logging
import os
import subprocess
from typing import Dict, Any
import shutil
import re
import tarfile
from cryptography import x509
from cryptography.hazmat.backends import default_backend
import logging
import time


def handle_logs(logger_name, log_file_name):
# Create a logger
logger = logging.getLogger(logger_name)

# Set the level of the logger. This can be DEBUG, INFO, WARNING, ERROR, CRITICAL.
logger.setLevel(logging.DEBUG)

# Create a file handler
handler = logging.FileHandler(f"/app/logs/{log_file_name}")

# Create a formatter and add it to the handler
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)

# Add the handler to the logger
logger.addHandler(handler)

return logger


logger = logging.getLogger(__name__)
bp = Blueprint('manager', __name__, url_prefix='/manager')
logger = handle_logs('web_manager', 'web_manager.log')


class ReverseProxyManager:
def __init__(self):
def __init__(self) -> None:
self.real_conf_path = '/etc/nginx/conf.d'
self.real_ssl_path = '/etc/nginx/certs'
self.real_log_path = '/var/log/nginx'
@@ -22,10 +48,33 @@ def __init__(self):
self.app_scripts_path = '/app/scripts'
self.app_nginx_path = '/app/nginx'
self.ip_pattern = r'^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$'
self.ssl_conf = {
'COUNTRY': os.getenv('SSL_COUNTRY', 'US'),
'STATE': os.getenv('SSL_STATE', 'California'),
'LOCATION': os.getenv('SSL_CITY', 'Los Angeles'),
'ORGANIZATION-GLOBAL': os.getenv('SSL_ORGANIZATION_GLOBAL', 'Company'),
'ORGANIZATION-UNIT': os.getenv('SSL_ORGANIZATION_UNIT', 'IT'),
'DAYS': os.getenv('SSL_DAYS', '365')
}

def reload_nginx(self) -> tuple[bool, str]:
with open(f'{self.app_scripts_path}/check_conf', 'w') as f:
f.write('check')

time.sleep(1)

with open(f'{self.app_scripts_path}/check_conf_status', 'r') as f:
return_code = f.readline().strip()

def reload_nginx(self) -> None:
with open(f'{self.app_scripts_path}/reload_nginx', 'w') as f:
f.write('reload')
if return_code != '0':
error = f.read().strip()
logger.error(f'Configuration check failed: {error}')
return False, error
else:
with open(f'{self.app_scripts_path}/reload_nginx', 'w') as f:
f.write('reload')
logger.info('Reloading Nginx')
return True, 'OK'

def address_check(self, server: str) -> bool:
if ':' in server:
@@ -46,7 +95,7 @@ def get_conf_list(self) -> list:
conf_list = []
for conf in os.listdir(self.app_conf_path):
conf_list.append(conf[:-5])
return conf_list
return sorted(conf_list)

def get_conf(self, conf_name: str) -> str:
with open(f'{self.app_conf_path}/{conf_name}.conf', 'r') as f:
@@ -55,37 +104,38 @@ def get_conf(self, conf_name: str) -> str:
def get_conf_infos(self, conf_name: str) -> Dict[str, Any]:
with open(f'{self.app_conf_path}/{conf_name}.conf', 'r') as f:
conf = f.read()
with open(f'{self.app_ssl_path}/{conf_name}.crt', 'rb') as f:
crt = f.read()
crt = x509.load_pem_x509_certificate(crt, default_backend())
infos = {
'name': conf_name,
'server_name': conf.split('server_name ')[1].split(';')[0],
'server': conf.split('proxy_pass ')[1].split(';')[0]
'server': conf.split('proxy_pass ')[1].split(';')[0],
'certificate': {
'subject': crt.subject.rfc4514_string(),
'issuer': crt.issuer.rfc4514_string(),
'serial_number': crt.serial_number,
'not_valid_before': crt.not_valid_before_utc,
'not_valid_after': crt.not_valid_after_utc
}
}
return infos

def get_ssl_conf(self) -> Dict[str, str]:
conf = {}
with open(f'{self.app_ssl_path}/ssl.conf', 'r') as f:
for line in f.readlines():
if '=' in line:
key, value = line.split('=')
conf[key.strip()] = value.strip()
return conf

def edit_conf(self,
conf_name: str,
conf_content: str,
cert_path: str = None,
key_path: str = None) -> None:
with open(f'{self.app_conf_path}/{conf_name}.conf', 'w') as f:
f.write(conf_content.strip() + '\n')
f.write(conf_content.replace('\r\n', '\n').strip() + '\n')

if cert_path and key_path:
shutil.copy(cert_path, f'{self.app_ssl_path}/{conf_name}.crt')
shutil.copy(key_path, f'{self.app_ssl_path}/{conf_name}.key')
os.remove(cert_path)
os.remove(key_path)

self.reload_nginx()
logger.info(f'Configuration {conf_name} edited')

def create_conf(self,
domain: str,
@@ -128,9 +178,8 @@ def create_conf(self,
add_header Cross-Origin-Opener-Policy "same-origin";
add_header Cross-Origin-Resource-Policy "same-site";
add_header Permissions-Policy ();
add_header Content-Security-Policy "default-src 'self'; img-src 'self' data: https: http:; script-src 'self' \
'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'";
# add_header Permissions-Policy ();
add_header Content-Security-Policy "default-src 'self'; img-src 'self' data: https: http:; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'";
proxy_cookie_flags ~ secure httponly samesite=strict;
@@ -157,10 +206,10 @@ def create_conf(self,
else:
self.generate_ssl(domain)

self.reload_nginx()
logger.info(f"Configuration {domain} created")

def generate_ssl(self, domain: str) -> None:
ssl_conf = self.get_ssl_conf()
ssl_conf = self.ssl_conf

ext_cnf_path = f'{self.app_ssl_path}/{domain}.ext.cnf'
with open(ext_cnf_path, 'w') as f:
@@ -182,54 +231,78 @@ def generate_ssl(self, domain: str) -> None:
[alt_names]
DNS.1 = {domain}
""")
subprocess.run([
'openssl', 'req', '-new', '-newkey', 'rsa:4096', '-sha256', '-days',
ssl_conf['DAYS'], '-nodes', '-x509', '-keyout', f'{self.app_ssl_path}/{domain}.key',
'-out', f'{self.app_ssl_path}/{domain}.crt',
'-subj', f"/C={ssl_conf['COUNTRY']}/ST={ssl_conf['STATE']}/L={ssl_conf['LOCATION']}"
f"/O={ssl_conf['ORGANIZATION-GLOBAL']}/OU={ssl_conf['ORGANIZATION-UNIT']}/CN={domain}",
'-config', ext_cnf_path
], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True)
try:
process = subprocess.run([
'openssl', 'req', '-new', '-newkey', 'rsa:4096', '-sha256', '-days',
ssl_conf['DAYS'], '-nodes', '-x509', '-keyout', f'{self.app_ssl_path}/{domain}.key',
'-out', f'{self.app_ssl_path}/{domain}.crt',
'-subj', f"/C={ssl_conf['COUNTRY']}/ST={ssl_conf['STATE']}/L={ssl_conf['LOCATION']}/O={ssl_conf['ORGANIZATION-GLOBAL']}/OU={ssl_conf['ORGANIZATION-UNIT']}/CN={domain}",
'-config', ext_cnf_path
], stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True)
except subprocess.CalledProcessError as e:
logger.error(f"Failed to generate SSL certificate for {domain}: {e}")
os.remove(ext_cnf_path)

logger.info(f"SSL certificate for {domain} generated")

def remove_conf(self, conf_name: str) -> None:
os.remove(f'{self.app_conf_path}/{conf_name}.conf')
os.remove(f'{self.app_ssl_path}/{conf_name}.crt')
os.remove(f'{self.app_ssl_path}/{conf_name}.key')
shutil.rmtree(f'{self.app_log_path}/{conf_name}', ignore_errors=True)

logger.info(f"Configuration {conf_name} removed")
self.reload_nginx()

def backup_nginx(self) -> None:
with tarfile.open(f'{self.app_scripts_path}/nginx.tar.gz', 'w:gz') as tar:
tar.add(self.app_nginx_path, arcname=os.path.basename(self.app_nginx_path))

def handle_cert_key_upload(self, cert: Any, key: Any, conf_name: str) -> tuple[str, str]:
if cert and key:
tmp_cert_path = f'{self.app_scripts_path}/{conf_name}.crt'
tmp_key_path = f'{self.app_scripts_path}/{conf_name}.key'
logger.info('Nginx configuration backed up')

def handle_cert_key_upload(self, conf_name: str, form_request: flask.Request) -> tuple[Any, Any]:
cert = form_request.files['cert'] if 'cert' in form_request.files else None
key = form_request.files['key'] if 'key' in form_request.files else None
cert_text = form_request.form['cert_text'] if 'cert_text' in form_request.form else None
key_text = form_request.form['key_text'] if 'key_text' in form_request.form else None

tmp_cert_path = f'{self.app_scripts_path}/{conf_name}.crt'
tmp_key_path = f'{self.app_scripts_path}/{conf_name}.key'

if (not cert and key) or (not cert_text and key_text):
tmp_cert_path = tmp_key_path = None
elif cert and key:
cert.save(tmp_cert_path)
key.save(tmp_key_path)
elif cert_text and key_text:
with open(tmp_cert_path, 'w') as f:
f.write(cert_text)
with open(tmp_key_path, 'w') as f:
f.write(key_text)
else:
tmp_cert_path = tmp_key_path = None
return tmp_cert_path, tmp_key_path


@bp.route('/manage', methods=['GET', 'POST'])
def manage():
def manage() -> str | flask.Response:
handler = ReverseProxyManager()
conf_list = handler.get_conf_list()
if request.method == 'POST':
if 'new_conf' in request.form:
new_conf = request.form['new_conf']
conf_name = request.form['conf_name']

cert = request.files['cert'] if 'cert' in request.files else None
key = request.files['key'] if 'key' in request.files else None
cert_path, key_path = handler.handle_cert_key_upload(cert, key, conf_name)
cert_path, key_path = handler.handle_cert_key_upload(conf_name, request)
handler.edit_conf(conf_name, new_conf, cert_path, key_path)
check, status = handler.reload_nginx()
if not check:
return render_template('manage.html', conf_list=conf_list, message='Failed to reload Nginx, error in configuration', error=status, success=False, conf_edit=new_conf, conf_name=conf_name)

handler.edit_conf(conf_name, new_conf.replace('\r\n', '\n'), cert_path, key_path)
if 'renew' in request.form:
handler.generate_ssl(conf_name)

return render_template('manage.html', conf_list=conf_list)
return render_template('manage.html', conf_list=conf_list, message='Configuration edited', success=True)

action = request.form['action']
conf_name = request.form['conf']
@@ -244,16 +317,20 @@ def manage():
return render_template('manage.html', conf_list=conf_list, conf_infos=conf_infos)
elif action == 'delete':
handler.remove_conf(conf_name)
return render_template('manage.html', conf_list=conf_list)
conf_list = handler.get_conf_list()
return render_template('manage.html', conf_list=conf_list, message='Configuration removed', success=True)
elif action == 'edit':
return render_template('manage.html', conf_list=conf_list,
conf_edit=conf_content, conf_name=conf_name)
elif action == 'logs':
return send_file(f'{handler.app_log_path}/{conf_name}/access.log',
download_name=f'{conf_name}.access.log', as_attachment=True)
else:
return render_template('manage.html', conf_list=conf_list)


@bp.route('/create', methods=['GET', 'POST'])
def create():
def create() -> str:
if request.method == 'POST':
handler = ReverseProxyManager()

@@ -269,23 +346,24 @@ def create():
if allow_origin == '':
allow_origin = '*'

cert = request.files['cert'] if 'cert' in request.files else None
key = request.files['key'] if 'key' in request.files else None
cert_path, key_path = handler.handle_cert_key_upload(cert, key, domain)
cert_path, key_path = handler.handle_cert_key_upload(domain, request)

if domain in handler.get_conf_list():
return render_template('create.html', message='Domain already exists', success=False)
if not handler.address_check(server):
return render_template('create.html', message='Invalid server address', success=False)
handler.create_conf(domain, server, description, service_type, allow_origin, cert_path, key_path)
check, status = handler.reload_nginx()
if not check:
return render_template('create.html', message='Configuration created but failed to reload Nginx, check error in conf', error=status, success=False)

return render_template('create.html', message='Configuration created', success=True)
else:
return render_template('create.html')


@bp.route('/backup', methods=['GET'])
def backup():
def backup() -> flask.Response:
handler = ReverseProxyManager()
handler.backup_nginx()
return send_from_directory(directory=f'{handler.app_scripts_path}', path='nginx.tar.gz', as_attachment=True)
4 changes: 3 additions & 1 deletion docker/web-manager/requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
flask
flask
cryptography
gunicorn
2 changes: 1 addition & 1 deletion docker/web-manager/static/css/bootstrap.min.css.map

Large diffs are not rendered by default.

89 changes: 87 additions & 2 deletions docker/web-manager/static/css/form.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,91 @@
.center-form {
display: flex;
justify-content: center;
}
.center-form > select {
margin: 0 15px;
}
.center-form > .btn {
height: 100%;
}

.create-form input {
margin: 0.5rem;
}

.form {
margin-top: 2rem;
margin-bottom: 2rem;
display: flex;
align-items: center;
justify-content: center;
color: var(--form-text-color);
}
.form > form {
width: 75%;
}
.form fieldset {
border: 2px solid var(--fieldset-border-color);
border-radius: 5px;
padding: 1rem;
}
.form fieldset > legend {
font-size: 1.5rem;
font-weight: bold;
color: var(--text-color);
float: none !important;
width: auto;
padding: 0 10px;
}
.form legend {
color: var(--text-color);
}
.form-group {
display: flex;
flex-direction: column;
align-items: center;
margin: 2rem;
}
justify-content: center;
}

.input-group {
display: flex;
align-content: stretch;
padding: 1rem;
}
.input-group > input, .input-group > textarea, .input-group > select {
flex: 1 0 auto;
font-family: Montserrat, sans-serif;
font-size: 1rem;
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
}
.input-group > input {
color: var(--text-color);
padding-left: .5rem;
}
.input-group > select {
background: var(--input-group-background-color);
}
.input-group .addon {
background: var(--input-group-addon-color);
border: 1px solid var(--input-group-addon-color);
border-radius: 5px;
padding: .5rem 1rem;
width: 150px;
}
.input-group .file {
border-radius: 5px;
padding: .5rem 1rem;
width: 150px;
border: 1px solid var(--input-group-addon-color);
background: var(--input-group-background-color);
margin-right: 1rem;
}
.input-group .btn {
width: 100%;
border-radius: 5px !important;
}
.input-group > textarea {
border: 1px solid var(--input-group-addon-color) !important;
margin-left: unset !important;
}
5 changes: 5 additions & 0 deletions docker/web-manager/static/css/import.css
Original file line number Diff line number Diff line change
@@ -5,9 +5,14 @@
@import 'footer.css';
@import 'button.css';
@import 'form.css';
@import 'manage.css';

@import url("https://fonts.googleapis.com/css2?family=Montserrat&family=Roboto&display=swap");

:root {
--text-color: rgb(33, 37, 41);
--form-text-color: rgb(211, 224, 234);
--input-group-background-color: rgb(255, 255, 255);
--input-group-addon-color: rgb(33, 37, 41);
--fieldset-border-color: rgb(33, 37, 41);
}
19 changes: 2 additions & 17 deletions docker/web-manager/static/css/main.css
Original file line number Diff line number Diff line change
@@ -35,16 +35,6 @@ body {
flex-flow: column;
}

.network_infos {
margin-top: 2rem;
}
.internet_status {
margin: 2rem;
}
.ping_infos {
margin: 1rem;
}

footer {
min-height: 50px;
position: relative;
@@ -64,11 +54,6 @@ footer a {
flex: 1;
}

.conf {
text-align: left;
}
.conf_edit {
width: 100%;
height: 100%;
min-height: 20rem;
.text-main {
color: var(--text-color);
}
30 changes: 30 additions & 0 deletions docker/web-manager/static/css/manage.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
.infos fieldset {
border: 2px solid var(--fieldset-border-color);
border-radius: 5px;
padding: 1rem;
margin: 2rem;
}
.infos fieldset > legend {
font-size: 1.5rem;
font-weight: bold;
color: var(--text-color);
float: none !important;
width: auto;
padding: 0 10px;
}
.infos fieldset > div {
text-align: left;
padding-left: 3rem;
}

.input-group .conf-edit {
width: 100% !important;
border-bottom-left-radius: 0 !important;
border-top-right-radius: 5px !important;
}
.conf-textarea {
margin-left: unset !important;
border-top-right-radius: 0 !important;
border-bottom-left-radius: 5px !important;
min-height: 20rem;
}
5 changes: 2 additions & 3 deletions docker/web-manager/templates/base.html
Original file line number Diff line number Diff line change
@@ -3,9 +3,8 @@
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="{{ url_for('static', filename='css/import.css') }}">
<link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='album/favicon.png') }}">
<link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='album/favicon.ico') }}">
<script src="/static/js/bootstrap.bundle.min.js"></script>
<meta http-equiv="refresh" content="30">
<title>{% block title %}{% endblock %}</title>
</head>
<body>
@@ -43,7 +42,7 @@
<div class="fill"></div>
<footer>
<p id="current-time"></p>
<p>&copy; Club*Nix • Louis GAMBART • Tous droits réservés • <span id="year"></span></p>
<p>&copy;Louis GAMBART • Tous droits réservés • <span id="year"></span></p>

<script>
document.getElementById('current-time').textContent = new Date().toLocaleString();
84 changes: 59 additions & 25 deletions docker/web-manager/templates/create.html
Original file line number Diff line number Diff line change
@@ -2,38 +2,72 @@
{% block title %}Reverse Proxy Manager{% endblock %}
{% block content %}
<div class="starter-template text-center py-5 px-3">
<h1>Create service</h1>
<br>
<form method="POST" enctype="multipart/form-data" class="row g-3 center-form">
<div>
<label for="domain">Domain:</label><br>
<input type="text" id="domain" name="domain" placeholder="example.com"><br>
<label for="server">Server:</label><br>
<input type="text" id="server" name="server" placeholder="127.0.0.1:8080"><br>
<label for="description">Description:</label><br>
<input type="text" id="description" name="description" placeholder="My service"><br>
<label for="allow_origin">Allow Origin:</label><br>
<input type="text" id="allow_origin" name="allow_origin" placeholder="*"><br>
<label for="service_type">Service Type:</label><br>
<select id="service_type" name="service_type">
<option value="http">HTTP</option>
<option value="https">HTTPS</option>
</select><br>
<label for="cert">Certificate:</label>
<input type="file" id="cert" name="cert" accept=".crt" placeholder="cert.crt"><br>
<label for="key">Key:</label>
<input type="file" id="key" name="key" accept=".key" placeholder="key.key"><br>
</div>
<div>
<button type="submit" class="btn btn-primary mb-3">Submit</button>
</div>
</form>

{% if message %}
<div class="{% if success %}text-success{% else %}text-danger{% endif %}">
<p>{{ message }}</p>
</div>
{% endif %}
{% if error %}
<div class="text-danger">
<p>{{ error }}</p>
</div>
{% endif %}

<div class="form">
<form method="POST" enctype="multipart/form-data">
<fieldset>
<legend>Create service</legend>
<div class="input-group">
<span class="input-group addon">Domain</span>
<label for="domain"></label>
<input type="text" id="domain" name="domain" placeholder="example.com" required>
</div>
<div class="input-group">
<span class="input-group addon">Server</span>
<label for="server"></label>
<input type="text" id="server" name="server" placeholder="127.0.0.1:5000" required>
</div>
<div class="input-group">
<span class="input-group addon">Description</span>
<label for="description"></label>
<input type="text" id="description" name="description" placeholder="My service">
</div>
<div class="input-group">
<span class="input-group addon">Allow Origin</span>
<label for="allow_origin"></label>
<input type="text" id="allow_origin" name="allow_origin" placeholder="*">
</div>
<div class="input-group">
<span class="input-group addon">Service Type</span>
<label for="service_type"></label>
<select id="service_type" name="service_type">
<option value="http">HTTP</option>
<option value="https">HTTPS</option>
</select>
</div>
<div class="input-group">
<span class="input-group addon">Certificate</span>
<label for="cert"></label>
<input class="input-group file" type="file" id="cert" name="cert" accept=".crt">
<label for="cert_text"></label>
<textarea id="cert_text" name="cert_text" placeholder="Or paste your certificate"></textarea>
</div>
<div class="input-group">
<span class="input-group addon">Key</span>
<label for="key"></label>
<input class="input-group file" type="file" id="key" name="key" accept=".key">
<label for="key_text"></label>
<textarea id="key_text" name="key_text" placeholder="Or paste your key"></textarea>
</div>

<div class="input-group">
<button type="submit" class="btn btn-primary mb-3">Submit</button>
</div>
</fieldset>
</form>
</div>
<br>
</div>
{% endblock %}
89 changes: 72 additions & 17 deletions docker/web-manager/templates/manage.html
Original file line number Diff line number Diff line change
@@ -4,28 +4,57 @@
<div class="starter-template text-center py-5 px-3">
<h1>Manage services</h1>
<br>

{% if message %}
<div class="{% if success %}text-success{% else %}text-danger{% endif %}">
<p>{{ message }}</p>
</div>
{% endif %}
{% if error %}
<div class="text-danger">
<p>{{ error }}</p>
</div>
{% endif %}

<form method="POST" class="row g-3 center-form">
<div class="col-auto">
<div class="col-auto center-form">
<label for="conf" class="visually-hidden">Configuration</label>
<select id="conf" name="conf" class="form-control">
<option selected>Choose...</option>
{% for conf in conf_list %}
<option value="{{ conf }}">{{ conf }}</option>
{% endfor %}
</select>
</div>
<div class="col-auto">
<button type="submit" name="action" value="view" class="btn btn-primary mb-3">View</button>
<button type="submit" name="action" value="delete" class="btn btn-primary mb-3">Delete</button>
<button type="submit" name="action" value="edit" class="btn btn-primary mb-3">Edit</button>
<button type="submit" name="action" value="logs" class="btn btn-primary mb-3">Logs</button>
</div>
</form>

{% if conf_infos %}
<div>
<p><strong>Name:</strong> {{ conf_infos.name }}</p>
<p><strong>Server Name:</strong> {{ conf_infos.server_name }}</p>
<p><strong>Server:</strong> {{ conf_infos.server }}</p>
<div class="infos">
<fieldset>
<legend>Global infos</legend>

<div>
<p><strong>Name:</strong> {{ conf_infos.name }}</p>
<p><strong>Server Name:</strong> {{ conf_infos.server_name }}</p>
<p><strong>Server:</strong> {{ conf_infos.server }}</p>
</div>
</fieldset>
<br>
<fieldset>
<legend>Certificate infos</legend>

<div>
<p><strong>Subject:</strong> {{ conf_infos.certificate.subject }}</p>
<p><strong>Issuer:</strong> {{ conf_infos.certificate.issuer }}</p>
<p><strong>Serial Number:</strong> {{ conf_infos.certificate.serial_number }}</p>
<p><strong>Not Valid Before:</strong> {{ conf_infos.certificate.not_valid_before }}</p>
<p><strong>Not Valid After:</strong> {{ conf_infos.certificate.not_valid_after }}</p>
</div>
</fieldset>
</div>
{% endif %}

@@ -36,16 +65,42 @@ <h1>Manage services</h1>
{% endif %}

{% if conf_edit %}
<form method="POST" enctype="multipart/form-data">
<label for="new_conf">New configuration:</label><br>
<textarea id="new_conf" name="new_conf" class="conf_edit">{{ conf_edit }}</textarea><br>
<label for="cert">Certificate:</label>
<input type="file" id="cert" name="cert" accept=".crt" placeholder="cert.crt"><br>
<label for="key">Key:</label>
<input type="file" id="key" name="key" accept=".key" placeholder="key.key"><br>
<input type="hidden" name="conf_name" value="{{ conf_name }}"><br>
<button type="submit" name="action" value="save" class="btn btn-primary mb-3">Save</button>
</form>
<div class="form">
<form method="POST" enctype="multipart/form-data">
<fieldset>
<legend>Edit a service</legend>
<div class="input-group">
<span class="input-group addon conf-edit">New configuration</span>
<label for="new_conf"></label>
<textarea id="new_conf" name="new_conf" class="conf-textarea">{{ conf_edit }}</textarea>
</div>
<div class="input-group">
<span class="input-group addon">Certificate</span>
<label for="cert"></label>
<input class="input-group file" type="file" id="cert" name="cert" accept=".crt">
<label for="cert_text"></label>
<textarea id="cert_text" name="cert_text" placeholder="Or paste your certificate"></textarea>
</div>
<div class="input-group">
<span class="input-group addon">Key</span>
<label for="key"></label>
<input class="input-group file" type="file" id="key" name="key" accept=".key">
<label for="key_text"></label>
<textarea id="key_text" name="key_text" placeholder="Or paste your key"></textarea>
</div>

<div class="text-main">
<input type="checkbox" id="renew" name="renew" value="renew">
<label for="renew">Renew certificate</label>
</div>

<div class="input-group">
<input type="hidden" name="conf_name" value="{{ conf_name }}"><br>
<button type="submit" class="btn btn-primary mb-3">Submit</button>
</div>
</fieldset>
</form>
</div>
{% endif %}
<br>
</div>