Added optional authentication and insert-highlighting
This commit is contained in:
parent
76d9f78b84
commit
68948d4f09
5 changed files with 133 additions and 14 deletions
|
@ -42,6 +42,8 @@ If you're using SSL, set the paths to your SSL files here. This is similar to th
|
||||||
The configurator fetches some data from your running HASS instance. If the API isn't available through the default URL, modify this variable to fix this.
|
The configurator fetches some data from your running HASS instance. If the API isn't available through the default URL, modify this variable to fix this.
|
||||||
####HASS_API_PASSWORD (string)
|
####HASS_API_PASSWORD (string)
|
||||||
If you plan on using the restart button, you have to set your API password. Calling the restart service of HASS is prohibited without authentication.
|
If you plan on using the restart button, you have to set your API password. Calling the restart service of HASS is prohibited without authentication.
|
||||||
|
####CREDENTIALS (string)
|
||||||
|
Set credentials in the form of `"username:password"` if authentication should be required for access.
|
||||||
|
|
||||||
###Embedding into HASS
|
###Embedding into HASS
|
||||||
HASS has the [panel_iframe](https://home-assistant.io/components/panel_iframe/) component. With this it is possible to embed the configurator directly into HASS, allowing you to modify your configuration through the HASS frontend.
|
HASS has the [panel_iframe](https://home-assistant.io/components/panel_iframe/) component. With this it is possible to embed the configurator directly into HASS, allowing you to modify your configuration through the HASS frontend.
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
Version 0.0.7 (2017-01-nn)
|
||||||
|
- Added optional authentication
|
||||||
|
- Inserted elements are selected as visual feedback
|
||||||
|
|
||||||
Version 0.0.6 (2017-01-29)
|
Version 0.0.6 (2017-01-29)
|
||||||
- Added SSL support
|
- Added SSL support
|
||||||
- Added HASS restart button
|
- Added HASS restart button
|
||||||
|
|
|
@ -9,6 +9,7 @@ import json
|
||||||
import ssl
|
import ssl
|
||||||
import socketserver
|
import socketserver
|
||||||
import urllib.request
|
import urllib.request
|
||||||
|
import base64
|
||||||
from string import Template
|
from string import Template
|
||||||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||||
from urllib.parse import urlparse, parse_qs
|
from urllib.parse import urlparse, parse_qs
|
||||||
|
@ -24,6 +25,8 @@ SSL_KEY = None
|
||||||
# Set the destination where the HASS API is reachable
|
# Set the destination where the HASS API is reachable
|
||||||
HASS_API = "http://127.0.0.1:8123/api/"
|
HASS_API = "http://127.0.0.1:8123/api/"
|
||||||
HASS_API_PASSWORD = None
|
HASS_API_PASSWORD = None
|
||||||
|
# To enable authentication, set the credentials in the form of "username:password"
|
||||||
|
CREDENTIALS = None
|
||||||
### End of options
|
### End of options
|
||||||
|
|
||||||
RELEASEURL = "https://api.github.com/repos/danielperna84/hass-poc-configurator/releases/latest"
|
RELEASEURL = "https://api.github.com/repos/danielperna84/hass-poc-configurator/releases/latest"
|
||||||
|
@ -109,7 +112,7 @@ INDEX = Template("""<!DOCTYPE html>
|
||||||
<div id="menu">
|
<div id="menu">
|
||||||
<div id="tree"></div><br />
|
<div id="tree"></div><br />
|
||||||
<label for="triggers">Trigger platforms:</label><br />
|
<label for="triggers">Trigger platforms:</label><br />
|
||||||
<select id="triggers" onchange="editor.session.insert(editor.getCursorPosition(), this.value)">
|
<select id="triggers" onchange="insert(this.value)">
|
||||||
<option value="" disabled selected>...</option>
|
<option value="" disabled selected>...</option>
|
||||||
<option value="event">Event</option>
|
<option value="event">Event</option>
|
||||||
<option value="mqtt">MQTT</option>
|
<option value="mqtt">MQTT</option>
|
||||||
|
@ -121,11 +124,11 @@ INDEX = Template("""<!DOCTYPE html>
|
||||||
<option value="zone">Zone</option>
|
<option value="zone">Zone</option>
|
||||||
</select><br /><br />
|
</select><br /><br />
|
||||||
<label for="events">Events:</label><br />
|
<label for="events">Events:</label><br />
|
||||||
<select id="events" onchange="editor.session.insert(editor.getCursorPosition(), this.value)"></select><br /><br />
|
<select id="events" onchange="insert(this.value)"></select><br /><br />
|
||||||
<label for="entities">Entities:</label><br />
|
<label for="entities">Entities:</label><br />
|
||||||
<select id="entities" onchange="editor.session.insert(editor.getCursorPosition(), this.value)"></select><br /><br />
|
<select id="entities" onchange="insert(this.value)"></select><br /><br />
|
||||||
<label for="conditions">Conditions:</label><br />
|
<label for="conditions">Conditions:</label><br />
|
||||||
<select id="conditions" onchange="editor.session.insert(editor.getCursorPosition(), this.value)">
|
<select id="conditions" onchange="insert(this.value)">
|
||||||
<option value="" disabled selected>...</option>
|
<option value="" disabled selected>...</option>
|
||||||
<option value="numeric_state">Numeric state</option>
|
<option value="numeric_state">Numeric state</option>
|
||||||
<option value="state">State</option>
|
<option value="state">State</option>
|
||||||
|
@ -135,7 +138,7 @@ INDEX = Template("""<!DOCTYPE html>
|
||||||
<option value="zone">Zone</option>
|
<option value="zone">Zone</option>
|
||||||
</select><br /><br />
|
</select><br /><br />
|
||||||
<label for="services">Services:</label><br />
|
<label for="services">Services:</label><br />
|
||||||
<select id="services" onchange="editor.session.insert(editor.getCursorPosition(), this.value)"></select>
|
<select id="services" onchange="insert(this.value)"></select>
|
||||||
</div>
|
</div>
|
||||||
<div id="toolbar">
|
<div id="toolbar">
|
||||||
<button id="savebutton" type="button" onclick="save()">Save</button>
|
<button id="savebutton" type="button" onclick="save()">Save</button>
|
||||||
|
@ -298,6 +301,11 @@ INDEX = Template("""<!DOCTYPE html>
|
||||||
editor.setOption("displayIndentGuides", true);
|
editor.setOption("displayIndentGuides", true);
|
||||||
editor.setOption("highlightSelectedWord", highlightwords);
|
editor.setOption("highlightSelectedWord", highlightwords);
|
||||||
editor.$blockScrolling = Infinity;
|
editor.$blockScrolling = Infinity;
|
||||||
|
function insert(text) {
|
||||||
|
var pos = editor.selection.getCursor();
|
||||||
|
var end = editor.session.insert(pos, text);
|
||||||
|
editor.selection.setRange({start:pos, end:end});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
</html>
|
</html>
|
||||||
""")
|
""")
|
||||||
|
@ -455,11 +463,60 @@ class RequestHandler(BaseHTTPRequestHandler):
|
||||||
self.wfile.write(bytes(response, "utf8"))
|
self.wfile.write(bytes(response, "utf8"))
|
||||||
return
|
return
|
||||||
|
|
||||||
|
class AuthHandler(RequestHandler):
|
||||||
|
def do_HEAD(self):
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header('Content-type', 'text/html')
|
||||||
|
self.end_headers()
|
||||||
|
|
||||||
|
def do_AUTHHEAD(self):
|
||||||
|
print("Requesting authorization")
|
||||||
|
self.send_response(401)
|
||||||
|
self.send_header('WWW-Authenticate', 'Basic realm=\"HASS-PoC-Configurator\"')
|
||||||
|
self.send_header('Content-type', 'text/html')
|
||||||
|
self.end_headers()
|
||||||
|
|
||||||
|
def do_GET(self):
|
||||||
|
global CREDENTIALS
|
||||||
|
authorization = self.headers.get('Authorization', None)
|
||||||
|
if authorization == None:
|
||||||
|
self.do_AUTHHEAD()
|
||||||
|
self.wfile.write(bytes('no auth header received', 'utf-8'))
|
||||||
|
pass
|
||||||
|
elif authorization == 'Basic %s' % CREDENTIALS.decode('utf-8'):
|
||||||
|
super().do_GET()
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
self.do_AUTHHEAD()
|
||||||
|
self.wfile.write(bytes('not authenticated', 'utf-8'))
|
||||||
|
pass
|
||||||
|
|
||||||
|
def do_POST(self):
|
||||||
|
global CREDENTIALS
|
||||||
|
authorization = self.headers.get('Authorization', None)
|
||||||
|
if authorization == None:
|
||||||
|
self.do_AUTHHEAD()
|
||||||
|
self.wfile.write(bytes('no auth header received', 'utf-8'))
|
||||||
|
pass
|
||||||
|
elif authorization == 'Basic %s' % CREDENTIALS.decode('utf-8'):
|
||||||
|
super().do_POST()
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
self.do_AUTHHEAD()
|
||||||
|
self.wfile.write(bytes('not authenticated', 'utf-8'))
|
||||||
|
pass
|
||||||
|
|
||||||
def run():
|
def run():
|
||||||
|
global CREDENTIALS
|
||||||
print('Starting server')
|
print('Starting server')
|
||||||
server_address = (LISTENIP, LISTENPORT)
|
server_address = (LISTENIP, LISTENPORT)
|
||||||
|
if CREDENTIALS:
|
||||||
|
CREDENTIALS = base64.b64encode(bytes(CREDENTIALS, "utf-8"))
|
||||||
|
Handler = AuthHandler
|
||||||
|
else:
|
||||||
|
Handler = RequestHandler
|
||||||
if not SSL_CERTIFICATE:
|
if not SSL_CERTIFICATE:
|
||||||
httpd = HTTPServer(server_address, RequestHandler)
|
httpd = HTTPServer(server_address, Handler)
|
||||||
else:
|
else:
|
||||||
httpd = socketserver.TCPServer(server_address, RequestHandler)
|
httpd = socketserver.TCPServer(server_address, RequestHandler)
|
||||||
httpd.socket = ssl.wrap_socket(httpd.socket, certfile=SSL_CERTIFICATE, keyfile=SSL_KEY, server_side=True)
|
httpd.socket = ssl.wrap_socket(httpd.socket, certfile=SSL_CERTIFICATE, keyfile=SSL_KEY, server_side=True)
|
||||||
|
|
|
@ -13,7 +13,7 @@ from string import Template
|
||||||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||||
from urllib.parse import urlparse, parse_qs
|
from urllib.parse import urlparse, parse_qs
|
||||||
|
|
||||||
### Some options for you to change
|
######### Some options for you to change #########
|
||||||
LISTENIP = "0.0.0.0"
|
LISTENIP = "0.0.0.0"
|
||||||
LISTENPORT = 3218
|
LISTENPORT = 3218
|
||||||
# Set BASEPATH to something like "/home/hass/.homeasssitant" if you're not running the configurator from that path
|
# Set BASEPATH to something like "/home/hass/.homeasssitant" if you're not running the configurator from that path
|
||||||
|
@ -23,7 +23,9 @@ SSL_CERTIFICATE = None
|
||||||
SSL_KEY = None
|
SSL_KEY = None
|
||||||
# Set the destination where the HASS API is reachable
|
# Set the destination where the HASS API is reachable
|
||||||
HASS_API = "http://127.0.0.1:8123/api/"
|
HASS_API = "http://127.0.0.1:8123/api/"
|
||||||
### End of options
|
# To enable authentication, set the credentials in the form of "username:password"
|
||||||
|
CREDENTIALS = None
|
||||||
|
################# End of options #################
|
||||||
|
|
||||||
RELEASEURL = "https://api.github.com/repos/danielperna84/hass-poc-configurator/releases/latest"
|
RELEASEURL = "https://api.github.com/repos/danielperna84/hass-poc-configurator/releases/latest"
|
||||||
VERSION = "0.0.6"
|
VERSION = "0.0.6"
|
||||||
|
@ -165,11 +167,60 @@ class RequestHandler(BaseHTTPRequestHandler):
|
||||||
self.wfile.write(bytes(response, "utf8"))
|
self.wfile.write(bytes(response, "utf8"))
|
||||||
return
|
return
|
||||||
|
|
||||||
|
class AuthHandler(RequestHandler):
|
||||||
|
def do_HEAD(self):
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header('Content-type', 'text/html')
|
||||||
|
self.end_headers()
|
||||||
|
|
||||||
|
def do_AUTHHEAD(self):
|
||||||
|
print("Requesting authorization")
|
||||||
|
self.send_response(401)
|
||||||
|
self.send_header('WWW-Authenticate', 'Basic realm=\"HASS-PoC-Configurator\"')
|
||||||
|
self.send_header('Content-type', 'text/html')
|
||||||
|
self.end_headers()
|
||||||
|
|
||||||
|
def do_GET(self):
|
||||||
|
global CREDENTIALS
|
||||||
|
authorization = self.headers.get('Authorization', None)
|
||||||
|
if authorization == None:
|
||||||
|
self.do_AUTHHEAD()
|
||||||
|
self.wfile.write(bytes('no auth header received', 'utf-8'))
|
||||||
|
pass
|
||||||
|
elif authorization == 'Basic %s' % CREDENTIALS.decode('utf-8'):
|
||||||
|
super().do_GET()
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
self.do_AUTHHEAD()
|
||||||
|
self.wfile.write(bytes('not authenticated', 'utf-8'))
|
||||||
|
pass
|
||||||
|
|
||||||
|
def do_POST(self):
|
||||||
|
global CREDENTIALS
|
||||||
|
authorization = self.headers.get('Authorization', None)
|
||||||
|
if authorization == None:
|
||||||
|
self.do_AUTHHEAD()
|
||||||
|
self.wfile.write(bytes('no auth header received', 'utf-8'))
|
||||||
|
pass
|
||||||
|
elif authorization == 'Basic %s' % CREDENTIALS.decode('utf-8'):
|
||||||
|
super().do_POST()
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
self.do_AUTHHEAD()
|
||||||
|
self.wfile.write(bytes('not authenticated', 'utf-8'))
|
||||||
|
pass
|
||||||
|
|
||||||
def run():
|
def run():
|
||||||
|
global CREDENTIALS
|
||||||
print('Starting server')
|
print('Starting server')
|
||||||
server_address = (LISTENIP, LISTENPORT)
|
server_address = (LISTENIP, LISTENPORT)
|
||||||
|
if CREDENTIALS:
|
||||||
|
CREDENTIALS = base64.b64encode(bytes(CREDENTIALS, "utf-8"))
|
||||||
|
Handler = AuthHandler
|
||||||
|
else:
|
||||||
|
Handler = RequestHandler
|
||||||
if not SSL_CERTIFICATE:
|
if not SSL_CERTIFICATE:
|
||||||
httpd = HTTPServer(server_address, RequestHandler)
|
httpd = HTTPServer(server_address, Handler)
|
||||||
else:
|
else:
|
||||||
httpd = socketserver.TCPServer(server_address, RequestHandler)
|
httpd = socketserver.TCPServer(server_address, RequestHandler)
|
||||||
httpd.socket = ssl.wrap_socket(httpd.socket, certfile=SSL_CERTIFICATE, keyfile=SSL_KEY, server_side=True)
|
httpd.socket = ssl.wrap_socket(httpd.socket, certfile=SSL_CERTIFICATE, keyfile=SSL_KEY, server_side=True)
|
||||||
|
|
|
@ -78,7 +78,7 @@
|
||||||
<div id="menu">
|
<div id="menu">
|
||||||
<div id="tree"></div><br />
|
<div id="tree"></div><br />
|
||||||
<label for="triggers">Trigger platforms:</label><br />
|
<label for="triggers">Trigger platforms:</label><br />
|
||||||
<select id="triggers" onchange="editor.session.insert(editor.getCursorPosition(), this.value)">
|
<select id="triggers" onchange="insert(this.value)">
|
||||||
<option value="" disabled selected>...</option>
|
<option value="" disabled selected>...</option>
|
||||||
<option value="event">Event</option>
|
<option value="event">Event</option>
|
||||||
<option value="mqtt">MQTT</option>
|
<option value="mqtt">MQTT</option>
|
||||||
|
@ -90,11 +90,11 @@
|
||||||
<option value="zone">Zone</option>
|
<option value="zone">Zone</option>
|
||||||
</select><br /><br />
|
</select><br /><br />
|
||||||
<label for="events">Events:</label><br />
|
<label for="events">Events:</label><br />
|
||||||
<select id="events" onchange="editor.session.insert(editor.getCursorPosition(), this.value)"></select><br /><br />
|
<select id="events" onchange="insert(this.value)"></select><br /><br />
|
||||||
<label for="entities">Entities:</label><br />
|
<label for="entities">Entities:</label><br />
|
||||||
<select id="entities" onchange="editor.session.insert(editor.getCursorPosition(), this.value)"></select><br /><br />
|
<select id="entities" onchange="insert(this.value)"></select><br /><br />
|
||||||
<label for="conditions">Conditions:</label><br />
|
<label for="conditions">Conditions:</label><br />
|
||||||
<select id="conditions" onchange="editor.session.insert(editor.getCursorPosition(), this.value)">
|
<select id="conditions" onchange="insert(this.value)">
|
||||||
<option value="" disabled selected>...</option>
|
<option value="" disabled selected>...</option>
|
||||||
<option value="numeric_state">Numeric state</option>
|
<option value="numeric_state">Numeric state</option>
|
||||||
<option value="state">State</option>
|
<option value="state">State</option>
|
||||||
|
@ -104,7 +104,7 @@
|
||||||
<option value="zone">Zone</option>
|
<option value="zone">Zone</option>
|
||||||
</select><br /><br />
|
</select><br /><br />
|
||||||
<label for="services">Services:</label><br />
|
<label for="services">Services:</label><br />
|
||||||
<select id="services" onchange="editor.session.insert(editor.getCursorPosition(), this.value)"></select>
|
<select id="services" onchange="insert(this.value)"></select>
|
||||||
</div>
|
</div>
|
||||||
<div id="toolbar">
|
<div id="toolbar">
|
||||||
<button id="savebutton" type="button" onclick="save()">Save</button>
|
<button id="savebutton" type="button" onclick="save()">Save</button>
|
||||||
|
@ -267,5 +267,10 @@
|
||||||
editor.setOption("displayIndentGuides", true);
|
editor.setOption("displayIndentGuides", true);
|
||||||
editor.setOption("highlightSelectedWord", highlightwords);
|
editor.setOption("highlightSelectedWord", highlightwords);
|
||||||
editor.$blockScrolling = Infinity;
|
editor.$blockScrolling = Infinity;
|
||||||
|
function insert(text) {
|
||||||
|
var pos = editor.selection.getCursor();
|
||||||
|
var end = editor.session.insert(pos, text);
|
||||||
|
editor.selection.setRange({start:pos, end:end});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
</html>
|
</html>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue