diff --git a/.gitignore b/.gitignore index 9ba829a..b8ab2c3 100644 --- a/.gitignore +++ b/.gitignore @@ -467,3 +467,5 @@ typings/ # Linux trash folder which might appear on any partition or disk # .nfs files are created when an open file is removed but is still being accessed + +docker-compose.windows.yml diff --git a/Dockerfile b/Dockerfile index 1173d01..ae095ab 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,15 @@ FROM python:3.7-alpine ADD requirements.txt . -RUN apk add python3-dev build-base linux-headers pcre-dev && pip install --no-cache-dir -r requirements.txt +RUN apk add curl python3-dev build-base linux-headers pcre-dev && pip install --no-cache-dir -r requirements.txt + +RUN cd /tmp/ \ + && curl -sSL -O https://download.docker.com/linux/static/stable/x86_64/docker-17.06.2-ce.tgz \ + && tar zxf docker-17.06.2-ce.tgz \ + && mkdir -p /usr/local/bin \ + && mv ./docker/docker /usr/local/bin \ + && chmod +x /usr/local/bin/docker \ + && rm -rf /tmp/* # adding application files ADD . /webapp @@ -10,6 +18,7 @@ ADD . /webapp # configure path /webapp to HOME-dir ENV HOME /webapp WORKDIR /webapp +EXPOSE 8080 ENTRYPOINT ["uwsgi"] CMD ["--http", "0.0.0.0:8080", "--wsgi-file", "wsgi.py", "--callable", "app", "--processes", "1", "--threads", "8"] \ No newline at end of file diff --git a/README.md b/README.md index e04e7a4..cf336b3 100644 --- a/README.md +++ b/README.md @@ -114,3 +114,67 @@ server { 2. Run `nginx -t` to make sure, that your config is valid 3. Run `systemctl restart nginx` (or equivalent) to restart your nginx and apply the new settings 4. Your nginx ui is now accessible at nginx.mydomain.com and will correctly prompt for basic auth + +### Example NginX-UI + NginX (run in container) behind path '/some-location' + +![Image of Nginx UI](https://i.ibb.co/59myNSf/nginx-ui.png) + +> If Nginx running as a container separatedly, '$ docker exec {nginx-container-name} nginx -s reload' button will be visible after docker socket and container name provided + +docker-compose.yml +```yaml +version: '3' +services: + nginx-ui: + container_name: nginx-ui + build: . + image: schenkd/nginx-ui:latest + # ports: + # - 8080:8080 + volumes: + - ./nginx/etc-nginx:/etc/nginx + - /var/run/docker.sock:/var/run/docker.sock + environment: + NGINX_CONTAINER_NAME: nginx + networks: + - my-custom-network + nginx: + container_name: nginx + image: nginx:latest + ports: + - 80:80 + volumes: + - ./nginx/etc-nginx:/etc/nginx + networks: + - my-custom-network +networks: + my-custom-network: + name: my-custom-network + external: true +``` + +default.conf +```none +server { + + listen 80; + listen [::]:80; + server_name localhost; + + # ... + + location /nginx-ui/ { + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # Need to pass this header for backend procesing route '/nginx-ui' + '/api' + proxy_set_header X-Forwarded-Prefix /nginx-ui; + + # With docker custom network, we can use container name with port + # We cannot access port 8080 directly from outside host server + proxy_pass http://nginx-ui:8080/; + } + +} +``` \ No newline at end of file diff --git a/app/__init__.py b/app/__init__.py index 7d8039c..d202e06 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -2,12 +2,18 @@ from config import config from flask_moment import Moment +from werkzeug.middleware.proxy_fix import ProxyFix moment = Moment() def create_app(config_name): app = Flask(__name__) + + app.wsgi_app = ProxyFix( + app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_prefix=1 + ) + app.config.from_object(config[config_name]) config[config_name].init_app(app) diff --git a/app/api/endpoints.py b/app/api/endpoints.py index df8f9e1..4e15e06 100644 --- a/app/api/endpoints.py +++ b/app/api/endpoints.py @@ -2,10 +2,38 @@ import io import os import flask +import subprocess +from pathlib import Path from app.api import api +@api.route('/reload-nginx', methods=['GET']) +def get_reload_nginx(): + """ + Runs the command to reload the nginx configuration. + + :return: Returns a status from terminal output. + :rtype: str + """ + exec_cmd = 'nginx -s reload' + res = 'No NginX docker container provided' + code = 400 + + docker_sock = Path('/var/run/docker.sock') + if docker_sock.is_socket() and 'NGINX_CONTAINER_NAME' in os.environ: + exec_cmd = f'docker exec {os.environ.get("NGINX_CONTAINER_NAME")} {exec_cmd}' + proc = subprocess.Popen(exec_cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out, err = proc.communicate() + if err: + res = str(err.decode('utf-8')) + else: + res = str(out.decode('utf-8')) + code = 200 + + return flask.make_response({'script': exec_cmd, 'result': res}), code + + @api.route('/config/', methods=['GET']) def get_config(name: str): """ diff --git a/app/static/custom.js b/app/static/custom.js index 3e55e1d..f9b91b7 100644 --- a/app/static/custom.js +++ b/app/static/custom.js @@ -12,6 +12,21 @@ $(document).ready(function() { }); +function reload_nginx() { + $.ajax({ + type: 'GET', + url: 'api/reload-nginx', + statusCode: { + 200: function() { + alert('NginX reloaded successfully'); + }, + 400: function() { + alert(`Failed to reload NginX`); + } + } + }); +} + function load_domains() { $.when(fetch_html('api/domains')).then(function() { $('#domain').hide(); @@ -25,7 +40,7 @@ function add_domain() { $.ajax({ type: 'POST', - url: '/api/domain/' + name, + url: 'api/domain/' + name, statusCode: { 201: function() { fetch_domain(name) } } @@ -37,7 +52,7 @@ function enable_domain(name, enable) { $.ajax({ type: 'POST', - url: '/api/domain/' + name + '/enable', + url: 'api/domain/' + name + '/enable', contentType: 'application/json; charset=utf-8', dataType: 'json', data: JSON.stringify({ @@ -56,7 +71,7 @@ function update_domain(name) { $.ajax({ type: 'PUT', - url: '/api/domain/' + name, + url: 'api/domain/' + name, contentType: 'application/json; charset=utf-8', dataType: 'json', data: JSON.stringify({ @@ -88,7 +103,7 @@ function remove_domain(name) { $.ajax({ type: 'DELETE', - url: '/api/domain/' + name, + url: 'api/domain/' + name, statusCode: { 200: function() { load_domains(); @@ -121,7 +136,7 @@ function update_config(name) { $.ajax({ type: 'POST', - url: '/api/config/' + name, + url: 'api/config/' + name, contentType: 'application/json; charset=utf-8', dataType: 'json', data: JSON.stringify({ diff --git a/app/static/custom.min.js b/app/static/custom.min.js index da19fda..6648591 100644 --- a/app/static/custom.min.js +++ b/app/static/custom.min.js @@ -1 +1 @@ -function load_domains(){$.when(fetch_html("api/domains")).then(function(){$("#domain").hide(),$("#domain_cards").fadeIn()})}function add_domain(){var n=$("#add_domain").val();$("#add_domain").val(""),$.ajax({type:"POST",url:"/api/domain/"+n,statusCode:{201:function(){fetch_domain(n)}}})}function enable_domain(n,t){$.ajax({type:"POST",url:"/api/domain/"+n+"/enable",contentType:"application/json; charset=utf-8",dataType:"json",data:JSON.stringify({enable:t}),statusCode:{200:function(){fetch_domain(n)}}})}function update_domain(n){var t=$("#file-content").val();$("#dimmer").addClass("active"),$.ajax({type:"PUT",url:"/api/domain/"+n,contentType:"application/json; charset=utf-8",dataType:"json",data:JSON.stringify({file:t}),statusCode:{200:function(){setTimeout(function(){fetch_domain(n)},400)}}})}function fetch_domain(n){fetch("api/domain/"+n).then(function(n){n.text().then(function(n){$("#domain").html(n).fadeIn(),$("#domain_cards").hide()})}).catch(function(n){console.error(n)})}function remove_domain(n){$.ajax({type:"DELETE",url:"/api/domain/"+n,statusCode:{200:function(){load_domains()},400:function(){alert("Deleting not possible")}}})}function fetch_html(n){fetch(n).then(function(n){n.text().then(function(n){$("#content").html(n)})}).catch(function(n){console.error(n)})}function update_config(n){var t=$("#file-content").val();$("#dimmer").addClass("active"),$.ajax({type:"POST",url:"/api/config/"+n,contentType:"application/json; charset=utf-8",dataType:"json",data:JSON.stringify({file:t}),statusCode:{200:function(){setTimeout(function(){$("#dimmer").removeClass("active")},450)}}})}function load_config(n){fetch("api/config/"+n).then(function(n){n.text().then(function(n){$("#content").html(n)})}).catch(function(n){console.error(n)})}$(document).ready(function(){$(".ui.dropdown").dropdown(),$(".config.item").click(function(){load_config($(this).html())}),$("#domains").click(function(){load_domains()}),load_domains()}); \ No newline at end of file +function reload_nginx(){$.ajax({type:"GET",url:"api/reload-nginx",statusCode:{200:function(){alert("NginX reloaded successfully")},400:function(){alert("Failed to reload NginX")}}})}function load_domains(){$.when(fetch_html("api/domains")).then(function(){$("#domain").hide(),$("#domain_cards").fadeIn()})}function add_domain(){var n=$("#add_domain").val();$("#add_domain").val(""),$.ajax({type:"POST",url:"api/domain/"+n,statusCode:{201:function(){fetch_domain(n)}}})}function enable_domain(n,t){$.ajax({type:"POST",url:"api/domain/"+n+"/enable",contentType:"application/json; charset=utf-8",dataType:"json",data:JSON.stringify({enable:t}),statusCode:{200:function(){fetch_domain(n)}}})}function update_domain(n){var t=$("#file-content").val();$("#dimmer").addClass("active"),$.ajax({type:"PUT",url:"api/domain/"+n,contentType:"application/json; charset=utf-8",dataType:"json",data:JSON.stringify({file:t}),statusCode:{200:function(){setTimeout(function(){fetch_domain(n)},400)}}})}function fetch_domain(n){fetch("api/domain/"+n).then(function(n){n.text().then(function(n){$("#domain").html(n).fadeIn(),$("#domain_cards").hide()})}).catch(function(n){console.error(n)})}function remove_domain(n){$.ajax({type:"DELETE",url:"api/domain/"+n,statusCode:{200:function(){load_domains()},400:function(){alert("Deleting not possible")}}})}function fetch_html(n){fetch(n).then(function(n){n.text().then(function(n){$("#content").html(n)})}).catch(function(n){console.error(n)})}function update_config(n){var t=$("#file-content").val();$("#dimmer").addClass("active"),$.ajax({type:"POST",url:"api/config/"+n,contentType:"application/json; charset=utf-8",dataType:"json",data:JSON.stringify({file:t}),statusCode:{200:function(){setTimeout(function(){$("#dimmer").removeClass("active")},450)}}})}function load_config(n){fetch("api/config/"+n).then(function(n){n.text().then(function(n){$("#content").html(n)})}).catch(function(n){console.error(n)})}$(document).ready(function(){$(".ui.dropdown").dropdown(),$(".config.item").click(function(){load_config($(this).html())}),$("#domains").click(function(){load_domains()}),load_domains()}); \ No newline at end of file diff --git a/app/templates/index.html b/app/templates/index.html index 9109245..c74929a 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -41,6 +41,11 @@