Skip to content

Commit 77142c9

Browse files
authored
v0.2
* New Layout * Navigation is now part of the menu * App is now responsive (on mobile and tablet) * Add documentation to endpoints
1 parent 7ad3802 commit 77142c9

File tree

11 files changed

+255
-182
lines changed

11 files changed

+255
-182
lines changed

.github/workflows/docker-image.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
name: Docker Image CI
2+
3+
on:
4+
push:
5+
branches: [ "master" ]
6+
7+
jobs:
8+
build:
9+
runs-on: ubuntu-latest
10+
steps:
11+
- name: Build the Docker image
12+
uses: actions/checkout@v2
13+
with:
14+
username: ${{ secrets.DOCKER_USERNAME }}
15+
password: ${{ secrets.DOCKER_PASSWORD }}
16+
repository: schenkd
17+
tag_with_ref: true

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# nginx ui
22

3+
![Image of Nginx UI](https://i.ibb.co/XXcfsDp/Bildschirmfoto-2020-06-20-um-18-40-27.png)
4+
35
We use nginx in our company lab environment. It often happens that my
46
colleagues have developed an application that is now deployed in our Stage
57
or Prod environment. To make this application accessible nginx has to be
@@ -28,3 +30,16 @@ services:
2830
- nginx:/etc/nginx
2931
```
3032
33+
## UI
34+
35+
![Image of Nginx UI](https://i.ibb.co/qNgBRrt/Bildschirmfoto-2020-06-21-um-10-01-46.png)
36+
37+
With the menu item Main Config the Nginx specific configuration files
38+
can be extracted and updated. These are dynamically read from the Nginx
39+
directory. If a file has been added manually, it is immediately integrated
40+
into the Nginx UI Main Config menu item.
41+
42+
![Image of Nginx UI](https://i.ibb.co/j85XKM6/Bildschirmfoto-2020-06-21-um-10-01-58.png)
43+
44+
Adding a domain opens an exclusive editing window for the configuration
45+
file. This can be applied, deleted and enabled/disabled.

app/api/endpoints.py

Lines changed: 86 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,41 @@
11
import datetime
22
import io
33
import os
4-
54
import flask
65

76
from app.api import api
87

98

109
@api.route('/config/<name>', methods=['GET'])
11-
def get_config(name):
10+
def get_config(name: str):
11+
"""
12+
Reads the file with the corresponding name that was passed.
13+
14+
:param name: Configuration file name
15+
:type name: str
16+
17+
:return: Rendered HTML document with content of the configuration file.
18+
:rtype: str
19+
"""
1220
nginx_path = flask.current_app.config['NGINX_PATH']
1321

1422
with io.open(os.path.join(nginx_path, name), 'r') as f:
1523
_file = f.read()
1624

17-
return flask.render_template('config.html', name=name, file=_file)
25+
return flask.render_template('config.html', name=name, file=_file), 200
1826

1927

2028
@api.route('/config/<name>', methods=['POST'])
21-
def post_config(name):
29+
def post_config(name: str):
30+
"""
31+
Accepts the customized configuration and saves it in the configuration file with the supplied name.
32+
33+
:param name: Configuration file name
34+
:type name: str
35+
36+
:return:
37+
:rtype: werkzeug.wrappers.Response
38+
"""
2239
content = flask.request.get_json()
2340
nginx_path = flask.current_app.config['NGINX_PATH']
2441

@@ -30,6 +47,12 @@ def post_config(name):
3047

3148
@api.route('/domains', methods=['GET'])
3249
def get_domains():
50+
"""
51+
Reads all files from the configuration file directory and checks the state of the site configuration.
52+
53+
:return: Rendered HTML document with the domains
54+
:rtype: str
55+
"""
3356
config_path = flask.current_app.config['CONFIG_PATH']
3457
sites_available = []
3558
sites_enabled = []
@@ -55,11 +78,23 @@ def get_domains():
5578
'time': time
5679
})
5780

58-
return flask.render_template('domains.html', sites_available=sites_available, sites_enabled=sites_enabled)
81+
# sort sites by name
82+
sites_available = sorted(sites_available, key=lambda _: _['name'])
83+
return flask.render_template('domains.html', sites_available=sites_available, sites_enabled=sites_enabled), 200
5984

6085

6186
@api.route('/domain/<name>', methods=['GET'])
62-
def get_domain(name):
87+
def get_domain(name: str):
88+
"""
89+
Takes the name of the domain configuration file and
90+
returns a rendered HTML with the current configuration of the domain.
91+
92+
:param name: The domain name that corresponds to the name of the file.
93+
:type name: str
94+
95+
:return: Rendered HTML document with the domain
96+
:rtype: str
97+
"""
6398
config_path = flask.current_app.config['CONFIG_PATH']
6499
_file = ''
65100
enabled = True
@@ -78,23 +113,44 @@ def get_domain(name):
78113

79114
break
80115

81-
return flask.render_template('domain.html', name=name, file=_file, enabled=enabled)
116+
return flask.render_template('domain.html', name=name, file=_file, enabled=enabled), 200
82117

83118

84119
@api.route('/domain/<name>', methods=['POST'])
85-
def post_domain(name):
120+
def post_domain(name: str):
121+
"""
122+
Creates the configuration file of the domain.
123+
124+
:param name: The domain name that corresponds to the name of the file.
125+
:type name: str
126+
127+
:return: Returns a status about the success or failure of the action.
128+
"""
86129
config_path = flask.current_app.config['CONFIG_PATH']
87130
new_domain = flask.render_template('new_domain.j2', name=name)
88131
name = name + '.conf.disabled'
89132

90-
with io.open(os.path.join(config_path, name), 'w') as f:
91-
f.write(new_domain)
133+
try:
134+
with io.open(os.path.join(config_path, name), 'w') as f:
135+
f.write(new_domain)
136+
137+
response = flask.jsonify({'success': True}), 201
138+
except Exception as ex:
139+
response = flask.jsonify({'success': False, 'error_msg': ex}), 500
92140

93-
return flask.jsonify({'success': True}), 201
141+
return response
94142

95143

96144
@api.route('/domain/<name>', methods=['DELETE'])
97-
def delete_domain(name):
145+
def delete_domain(name: str):
146+
"""
147+
Deletes the configuration file of the corresponding domain.
148+
149+
:param name: The domain name that corresponds to the name of the file.
150+
:type name: str
151+
152+
:return: Returns a status about the success or failure of the action.
153+
"""
98154
config_path = flask.current_app.config['CONFIG_PATH']
99155
removed = False
100156

@@ -113,7 +169,15 @@ def delete_domain(name):
113169

114170

115171
@api.route('/domain/<name>', methods=['PUT'])
116-
def put_domain(name):
172+
def put_domain(name: str):
173+
"""
174+
Updates the configuration file with the corresponding domain name.
175+
176+
:param name: The domain name that corresponds to the name of the file.
177+
:type name: str
178+
179+
:return: Returns a status about the success or failure of the action.
180+
"""
117181
content = flask.request.get_json()
118182
config_path = flask.current_app.config['CONFIG_PATH']
119183

@@ -128,7 +192,15 @@ def put_domain(name):
128192

129193

130194
@api.route('/domain/<name>/enable', methods=['POST'])
131-
def enable_domain(name):
195+
def enable_domain(name: str):
196+
"""
197+
Activates the domain in Nginx so that the configuration is applied.
198+
199+
:param name: The domain name that corresponds to the name of the file.
200+
:type name: str
201+
202+
:return: Returns a status about the success or failure of the action.
203+
"""
132204
content = flask.request.get_json()
133205
config_path = flask.current_app.config['CONFIG_PATH']
134206

app/static/custom.css

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,18 @@ textarea {
1313

1414
#main-container {
1515
margin-top: 5em;
16+
}
17+
#domain {
18+
display: none;
19+
}
20+
21+
@media only screen and (max-width: 666px) {
22+
[class*="mobile hidden"],
23+
[class*="tablet only"]:not(.mobile),
24+
[class*="computer only"]:not(.mobile),
25+
[class*="large monitor only"]:not(.mobile),
26+
[class*="widescreen monitor only"]:not(.mobile),
27+
[class*="or lower hidden"] {
28+
display: none !important;
29+
}
1630
}

app/static/custom.js

Lines changed: 12 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4,37 +4,30 @@ $(document).ready(function() {
44
$('.config.item').click(function() {
55
var name = $(this).html();
66
load_config(name);
7-
8-
$('.green.highlighted').removeClass('green highlighted');
9-
$('#edit_config').addClass('green highlighted');
107
});
118

12-
$('#domains').click(function() {
13-
$.when(load_domains()).then(function() {
14-
$('.green.highlighted').removeClass('green highlighted');
15-
$('#domains').addClass('green highlighted');
16-
});
9+
$('#domains').click(function() { load_domains() });
1710

18-
});
11+
load_domains();
1912

2013
});
2114

2215
function load_domains() {
23-
fetch_html('api/domains');
16+
$.when(fetch_html('api/domains')).then(function() {
17+
$('#domain').hide();
18+
$('#domain_cards').fadeIn();
19+
});
2420
}
2521

2622
function add_domain() {
2723
var name = $('#add_domain').val();
24+
$('#add_domain').val('');
2825

2926
$.ajax({
3027
type: 'POST',
3128
url: '/api/domain/' + name,
3229
statusCode: {
33-
201: function() {
34-
$.when(load_domains()).then(function() {
35-
fetch_domain(name);
36-
});
37-
}
30+
201: function() { fetch_domain(name) }
3831
}
3932
});
4033

@@ -51,11 +44,7 @@ function enable_domain(name, enable) {
5144
enable: enable
5245
}),
5346
statusCode: {
54-
200: function() {
55-
$.when(load_domains()).then(function() {
56-
fetch_domain(name);
57-
});
58-
}
47+
200: function() { fetch_domain(name); }
5948
}
6049
});
6150

@@ -74,20 +63,7 @@ function update_domain(name) {
7463
file: _file
7564
}),
7665
statusCode: {
77-
200: function() {
78-
79-
setTimeout(function(){
80-
81-
$.when(load_domains()).then(function() {
82-
83-
setTimeout(function() {
84-
fetch_domain(name);
85-
}, 50);
86-
87-
});
88-
}, 450);
89-
90-
}
66+
200: function() { setTimeout(function(){ fetch_domain(name) }, 400) }
9167
}
9268
});
9369

@@ -98,7 +74,8 @@ function fetch_domain(name) {
9874
fetch('api/domain/' + name)
9975
.then(function(response) {
10076
response.text().then(function(text) {
101-
$('#domain').html(text);
77+
$('#domain').html(text).fadeIn();
78+
$('#domain_cards').hide();
10279
});
10380
})
10481
.catch(function(error) {

app/static/custom.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/templates/config.html

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,33 @@
1-
<div class="column">
1+
<div class="ui top attached segment">
2+
<div class="ui two column grid">
23

4+
<div class="column">
5+
<h1 class="ui header">{{ name }}</h1>
6+
</div>
7+
8+
<div class="column">
9+
<button class="ui right floated labeled icon button" onclick="update_config('{{ name }}')" tabindex="0">
10+
<i class="play icon"></i>
11+
Apply
12+
</button>
13+
</div>
14+
15+
</div>
16+
</div>
17+
18+
<div class="ui attached segment">
319
<div class="ui form">
420

521
<div class="field">
6-
<label><h1>{{ name }}</h1></label>
22+
723
<div class="ui basic segment">
824
<div class="ui inverted dimmer" id="dimmer">
925
<div class="ui huge loader"></div>
1026
</div>
1127
<textarea class="code" rows="25" id="file-content" autofocus>{{ file }}</textarea>
1228
</div>
13-
</div>
1429

15-
<button class="ui right floated labeled icon button" onclick="update_config('{{ name }}')" tabindex="0">
16-
<i class="play icon"></i>
17-
Apply
18-
</button>
30+
</div>
1931

2032
</div>
21-
2233
</div>

0 commit comments

Comments
 (0)