Skip to content

Commit 3cc6ad9

Browse files
author
David Schenk
committed
v0.1 basic access to config
* read/write nginx config files * add, remove, update, enable and disable site configurations
1 parent c3bf438 commit 3cc6ad9

21 files changed

+715
-12
lines changed

Dockerfile

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
FROM python:3.7-alpine
2+
3+
ADD requirements.txt .
4+
5+
RUN apk add python3-dev build-base linux-headers pcre-dev && pip install --no-cache-dir -r requirements.txt
6+
7+
# adding application files
8+
ADD . /webapp
9+
10+
# configure path /webapp to HOME-dir
11+
ENV HOME /webapp
12+
WORKDIR /webapp
13+
14+
ENTRYPOINT ["uwsgi"]
15+
CMD ["--http", "0.0.0.0:8080", "--wsgi-file", "wsgi.py", "--callable", "app", "--processes", "1", "--threads", "8"]

README.md

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,30 @@
11
# nginx ui
22

3-
We use nginx in our company lab environment.
4-
I thought it would be nice to have the possibility to view and adjust
5-
the configuration of nginx via web ui.
3+
We use nginx in our company lab environment. It often happens that my
4+
colleagues have developed an application that is now deployed in our Stage
5+
or Prod environment. To make this application accessible nginx has to be
6+
adapted. Most of the time my colleagues don't have the permission to access
7+
the server and change the configuration files and since I don't feel like
8+
doing this for everyone anymore I thought a UI could help us all. If you
9+
feel the same way I wish you a lot of fun with the application and I am
10+
looking forward to your feedback, change requests or even a star.
11+
12+
## setup
13+
14+
Containerization is now state of the art and therefore the application is
15+
delivered in a container.
16+
17+
### docker
18+
19+
Repository @ [DockerHub](https://hub.docker.com/r/schenkd/nginx-ui)
20+
21+
```yaml
22+
services:
23+
nginx-ui:
24+
image: nginx-ui:latest
25+
ports:
26+
- 8080:8080
27+
volumes:
28+
- nginx:/etc/nginx
29+
```
30+

app/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
from flask import Flask
22
from config import config
3+
from flask_moment import Moment
4+
5+
6+
moment = Moment()
37

48

59
def create_app(config_name):
610
app = Flask(__name__)
711
app.config.from_object(config[config_name])
12+
813
config[config_name].init_app(app)
14+
moment.init_app(app)
915

1016
from app.ui import ui as ui_blueprint
1117
app.register_blueprint(ui_blueprint)

app/api/endpoints.py

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import datetime
2+
import io
3+
import os
4+
5+
import flask
6+
7+
from app.api import api
8+
9+
10+
@api.route('/config/<name>', methods=['GET'])
11+
def get_config(name):
12+
nginx_path = flask.current_app.config['NGINX_PATH']
13+
14+
with io.open(os.path.join(nginx_path, name), 'r') as f:
15+
_file = f.read()
16+
17+
return flask.render_template('config.html', name=name, file=_file)
18+
19+
20+
@api.route('/config/<name>', methods=['POST'])
21+
def post_config(name):
22+
content = flask.request.get_json()
23+
nginx_path = flask.current_app.config['NGINX_PATH']
24+
25+
with io.open(os.path.join(nginx_path, name), 'w') as f:
26+
f.write(content['file'])
27+
28+
return flask.make_response({'success': True}), 200
29+
30+
31+
@api.route('/domains', methods=['GET'])
32+
def get_domains():
33+
config_path = flask.current_app.config['CONFIG_PATH']
34+
sites_available = []
35+
sites_enabled = []
36+
37+
for _ in os.listdir(config_path):
38+
39+
if os.path.isfile(os.path.join(config_path, _)):
40+
domain, state = _.rsplit('.', 1)
41+
42+
if state == 'conf':
43+
time = datetime.datetime.fromtimestamp(os.path.getmtime(os.path.join(config_path, _)))
44+
45+
sites_available.append({
46+
'name': domain,
47+
'time': time
48+
})
49+
sites_enabled.append(domain)
50+
elif state == 'disabled':
51+
time = datetime.datetime.fromtimestamp(os.path.getmtime(os.path.join(config_path, _)))
52+
53+
sites_available.append({
54+
'name': domain.rsplit('.', 1)[0],
55+
'time': time
56+
})
57+
58+
return flask.render_template('domains.html', sites_available=sites_available, sites_enabled=sites_enabled)
59+
60+
61+
@api.route('/domain/<name>', methods=['GET'])
62+
def get_domain(name):
63+
config_path = flask.current_app.config['CONFIG_PATH']
64+
_file = ''
65+
enabled = True
66+
67+
for _ in os.listdir(config_path):
68+
69+
if os.path.isfile(os.path.join(config_path, _)):
70+
if _.startswith(name):
71+
domain, state = _.rsplit('.', 1)
72+
73+
if state == 'disabled':
74+
enabled = False
75+
76+
with io.open(os.path.join(config_path, _), 'r') as f:
77+
_file = f.read()
78+
79+
break
80+
81+
return flask.render_template('domain.html', name=name, file=_file, enabled=enabled)
82+
83+
84+
@api.route('/domain/<name>', methods=['POST'])
85+
def post_domain(name):
86+
config_path = flask.current_app.config['CONFIG_PATH']
87+
new_domain = flask.render_template('new_domain.j2', name=name)
88+
name = name + '.conf.disabled'
89+
90+
with io.open(os.path.join(config_path, name), 'w') as f:
91+
f.write(new_domain)
92+
93+
return flask.jsonify({'success': True}), 201
94+
95+
96+
@api.route('/domain/<name>', methods=['DELETE'])
97+
def delete_domain(name):
98+
config_path = flask.current_app.config['CONFIG_PATH']
99+
removed = False
100+
101+
for _ in os.listdir(config_path):
102+
103+
if os.path.isfile(os.path.join(config_path, _)):
104+
if _.startswith(name):
105+
os.remove(os.path.join(config_path, _))
106+
removed = not os.path.exists(os.path.join(config_path, _))
107+
break
108+
109+
if removed:
110+
return flask.jsonify({'success': True}), 200
111+
else:
112+
return flask.jsonify({'success': False}), 400
113+
114+
115+
@api.route('/domain/<name>', methods=['PUT'])
116+
def put_domain(name):
117+
content = flask.request.get_json()
118+
config_path = flask.current_app.config['CONFIG_PATH']
119+
120+
for _ in os.listdir(config_path):
121+
122+
if os.path.isfile(os.path.join(config_path, _)):
123+
if _.startswith(name):
124+
with io.open(os.path.join(config_path, _), 'w') as f:
125+
f.write(content['file'])
126+
127+
return flask.make_response({'success': True}), 200
128+
129+
130+
@api.route('/domain/<name>/enable', methods=['POST'])
131+
def enable_domain(name):
132+
content = flask.request.get_json()
133+
config_path = flask.current_app.config['CONFIG_PATH']
134+
135+
for _ in os.listdir(config_path):
136+
137+
if os.path.isfile(os.path.join(config_path, _)):
138+
if _.startswith(name):
139+
if content['enable']:
140+
new_filename, disable = _.rsplit('.', 1)
141+
os.rename(os.path.join(config_path, _), os.path.join(config_path, new_filename))
142+
else:
143+
os.rename(os.path.join(config_path, _), os.path.join(config_path, _ + '.disabled'))
144+
145+
return flask.make_response({'success': True}), 200

app/static/custom.css

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
@import url('https://fonts.googleapis.com/css?family=Source+Code+Pro');
2+
3+
textarea {
4+
margin: 0;
5+
border-radius: 0;
6+
}
7+
8+
.code {
9+
font-size: 17px;
10+
font-weight: 200;
11+
font-family: 'Source Code Pro', monospace;
12+
}
13+
14+
#main-container {
15+
margin-top: 5em;
16+
}

0 commit comments

Comments
 (0)