1242 lines
58 KiB
Python
Executable file
1242 lines
58 KiB
Python
Executable file
#!/usr/bin/python3
|
|
"""
|
|
Proof of concept configurator for Home Assistant.
|
|
https://github.com/danielperna84/hass-poc-configurator
|
|
"""
|
|
import os
|
|
import sys
|
|
import json
|
|
import ssl
|
|
import socketserver
|
|
import base64
|
|
import ipaddress
|
|
import signal
|
|
import urllib.request
|
|
from string import Template
|
|
from http.server import BaseHTTPRequestHandler, HTTPServer
|
|
from urllib.parse import urlparse, parse_qs, unquote
|
|
|
|
### Some options for you to change
|
|
LISTENIP = "0.0.0.0"
|
|
LISTENPORT = 3218
|
|
# Set BASEPATH to something like "/home/hass/.homeasssitant" if you're not running the configurator from that path
|
|
BASEPATH = None
|
|
# Set the paths to a certificate and the key if you're using SSL, e.g "/etc/ssl/certs/mycert.pem"
|
|
SSL_CERTIFICATE = None
|
|
SSL_KEY = None
|
|
# Set the destination where the HASS API is reachable
|
|
HASS_API = "http://127.0.0.1:8123/api/"
|
|
# If a password is required to access the API, set it in the form of "password"
|
|
HASS_API_PASSWORD = None
|
|
# To enable authentication, set the credentials in the form of "username:password"
|
|
CREDENTIALS = None
|
|
# Limit access to the configurator by adding allowed IP addresses / networks to the list,
|
|
# e.g ALLOWED_NETWORKS = ["192.168.0.0/24", "172.16.47.23"]
|
|
ALLOWED_NETWORKS = []
|
|
# List of statically banned IP addresses, e.g. ["1.1.1.1", "2.2.2.2"]
|
|
BANNED_IPS = []
|
|
# Ban IPs after n failed login attempts. Restart service to reset banning. The default of `0` disables this feature.
|
|
BANLIMIT = 0
|
|
### End of options
|
|
|
|
RELEASEURL = "https://api.github.com/repos/danielperna84/hass-poc-configurator/releases/latest"
|
|
VERSION = "0.1.0"
|
|
BASEDIR = "."
|
|
DEV = False
|
|
HTTPD = None
|
|
FAIL2BAN_IPS = {}
|
|
INDEX = Template("""<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0" />
|
|
<title>HASS Configurator</title>
|
|
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.98.0/css/materialize.min.css">
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.1/jquery.min.js"></script>
|
|
<style type="text/css" media="screen">
|
|
body {
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
|
|
#editor {
|
|
position: fixed;
|
|
top: 68px;
|
|
right: 0;
|
|
bottom: 0;
|
|
}
|
|
|
|
#fbheader {
|
|
display: block;
|
|
cursor: initial;
|
|
pointer-events: none;
|
|
color: rgba(0, 0, 0, 0.54);
|
|
font-size: 15px;
|
|
font-weight: 500;
|
|
line-height: 48px;
|
|
background-color: #eeeeee;
|
|
}
|
|
|
|
a.collection-item {
|
|
color: #616161 !important;
|
|
}
|
|
|
|
.filename {
|
|
margin-left: 10px;
|
|
}
|
|
|
|
.green {
|
|
color: #0f0;
|
|
}
|
|
|
|
.red {
|
|
color: #f00;
|
|
}
|
|
|
|
.dropdown-content {
|
|
min-width: 200px;
|
|
}
|
|
|
|
.dropdown-content li > a,
|
|
.dropdown-content li > span {
|
|
color: #616161 !important;
|
|
}
|
|
|
|
.blue_check:checked + label:before {
|
|
border-right: 2px solid #03a9f4;
|
|
border-bottom: 2px solid #03a9f4;
|
|
}
|
|
|
|
.input-field input:focus + label {
|
|
color: #03a9f4 !important;
|
|
}
|
|
|
|
.row .input-field input:focus {
|
|
border-bottom: 1px solid #03a9f4 !important;
|
|
box-shadow: 0 1px 0 0 #03a9f4 !important
|
|
}
|
|
|
|
.ace_optionsMenuEntry input {
|
|
position: relative !important;
|
|
left: 0 !important;
|
|
opacity: 100 !important;
|
|
}
|
|
|
|
.ace_optionsMenuEntry select {
|
|
position: relative !important;
|
|
left: 0 !important;
|
|
opacity: 100 !important;
|
|
display: block !important;
|
|
}
|
|
</style>
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.6/ace.js" type="text/javascript" charset="utf-8"></script>
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.6/ext-modelist.js" type="text/javascript" charset="utf-8"></script>
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.6/ext-language_tools.js" type="text/javascript" charset="utf-8"></script>
|
|
</head>
|
|
|
|
<body>
|
|
<div class="navbar-fixed">
|
|
<nav class="light-blue">
|
|
<div class="nav-wrapper">
|
|
<ul class="left">
|
|
<li><a href="#" data-activates="slide-out" class="waves-effect waves-light button-collapse show-on-large"><i class="material-icons">folder</i></a></li>
|
|
</ul>
|
|
<ul class="right">
|
|
<li><a class="waves-effect waves-light" href="#modal_save"><i class="material-icons">save</i></a></li>
|
|
<li><a class="waves-effect waves-light dropdown-button" href="#!" data-activates="dropdown_tools" data-beloworigin="true"><i class="material-icons right">more_vert</i></a></li>
|
|
</ul>
|
|
</div>
|
|
</nav>
|
|
</div>
|
|
<ul id="dropdown_tools" class="dropdown-content z-depth-4">
|
|
<li><a target="_blank" href="https://home-assistant.io/help/">Need HASS Help?</a></li>
|
|
<li><a target="_blank" href="https://home-assistant.io/components/">HASS Components</a></li>
|
|
<li><a href="#modal_about">About PoC</a></li>
|
|
<li class="divider"></li>
|
|
<li><a href="#" data-activates="ace_settings" class="ace_settings-collapse">Editor Settings</a></li>
|
|
<li><a href="#modal_restart">Restart HASS</a></li>
|
|
</ul>
|
|
<div id="modal_save" class="modal">
|
|
<div class="modal-content">
|
|
<h4>Save</h4>
|
|
<p>Do you really want to save?</p>
|
|
</div>
|
|
<div class="modal-footer"> <a href="#!" class=" modal-action modal-close waves-effect waves-red btn-flat">No</a> <a onclick="save()" class=" modal-action modal-close waves-effect waves-green btn-flat">Yes</a> </div>
|
|
</div>
|
|
<div id="modal_restart" class="modal">
|
|
<div class="modal-content">
|
|
<h4>Restart</h4>
|
|
<p>Do you really want to restart HASS?</p>
|
|
</div>
|
|
<div class="modal-footer"> <a href="#!" class=" modal-action modal-close waves-effect waves-red btn-flat">No</a> <a onclick="restart()" class=" modal-action modal-close waves-effect waves-green btn-flat">Yes</a> </div>
|
|
</div>
|
|
<div id="modal_about" class="modal">
|
|
<div class="modal-content">
|
|
<h4><a href="https://github.com/danielperna84/hass-poc-configurator/" target="_blank">HASS Configurator</a></h4>
|
|
<p>Version: <a class="$versionclass" href="https://github.com/danielperna84/hass-poc-configurator/releases/latest" target="_blank">$current</a></p>
|
|
<p>Web-based file editor designed to modify configuration files of <a href="https://home-assistant.io/" target="_blank">Home Assistant</a> or other textual files. Use at your own risk.</p>
|
|
<p>Published under the MIT license</p>
|
|
<p>Developed by:<br />
|
|
<ul>
|
|
<li><a href="https://github.com/danielperna84" target="_blank">Daniel Perna</a></li>
|
|
<li><a href="https://github.com/jmart518" target="_blank">JT Martinez</a></li>
|
|
</ul>
|
|
</p>
|
|
<p>Libraries used:<br />
|
|
<ul>
|
|
<li><a href="https://ace.c9.io/" target="_blank">Ace</a></li>
|
|
<li><a href="http://materializecss.com/" target="_blank">Materialize</a></li>
|
|
<li><a href="https://jquery.com/" target="_blank">jQuery</a></li>
|
|
</ul>
|
|
</p>
|
|
</div>
|
|
<div class="modal-footer"> <a class=" modal-action modal-close waves-effect btn-flat">OK</a> </div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col m4 l3 hide-on-small-only">
|
|
</br>
|
|
<div class="input-field col s12">
|
|
<select onchange="insert(this.value)">
|
|
<option value="" disabled selected>Choose your option</option>
|
|
<option value="event">Event</option>
|
|
<option value="mqtt">MQTT</option>
|
|
<option value="numberic_state">Numeric State</option>
|
|
<option value="state">State</option>
|
|
<option value="sun">Sun</option>
|
|
<option value="template">Template</option>
|
|
<option value="time">Time</option>
|
|
<option value="zone">Zone</option>
|
|
</select>
|
|
<label>Trigger Platform</label>
|
|
</div>
|
|
<div class="input-field col s12">
|
|
<select id="events" onchange="insert(this.value)"> </select>
|
|
<label>Events</label>
|
|
</div>
|
|
<div class="input-field col s12">
|
|
<select id="entities" onchange="insert(this.value)"> </select>
|
|
<label>Entities</label>
|
|
</div>
|
|
<div class="input-field col s12">
|
|
<select onchange="insert(this.value)">
|
|
<option value="" disabled selected>Choose your option</option>
|
|
<option value="numeric_state">Numeric state</option>
|
|
<option value="state">State</option>
|
|
<option value="sun">Sun</option>
|
|
<option value="template">Template</option>
|
|
<option value="time">Time</option>
|
|
<option value="zone">Zone</option>
|
|
</select>
|
|
<label>Conditions</label>
|
|
</div>
|
|
<div class="input-field col s12">
|
|
<select id="services" onchange="insert(this.value)"> </select>
|
|
<label>Services</label>
|
|
</div>
|
|
</div>
|
|
<div class="col s12 m8 l9 hoverable" id="editor"></div>
|
|
</div>
|
|
<div>
|
|
<ul id="slide-out" class="side-nav">
|
|
<div id="filebrowser"></div>
|
|
<div class="row">
|
|
<div class="hide-on-med-and-up">
|
|
<div class="input-field col s12">
|
|
<select onchange="insert(this.value)">
|
|
<option value="" disabled selected>Choose your option</option>
|
|
<option value="event">Event</option>
|
|
<option value="mqtt">MQTT</option>
|
|
<option value="numberic_state">Numeric State</option>
|
|
<option value="state">State</option>
|
|
<option value="sun">Sun</option>
|
|
<option value="template">Template</option>
|
|
<option value="time">Time</option>
|
|
<option value="zone">Zone</option>
|
|
</select>
|
|
<label>Trigger Platform</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="hide-on-med-and-up">
|
|
<div class="input-field col s12">
|
|
<select id="events_side" onchange="insert(this.value)"> </select>
|
|
<label>Events</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="hide-on-med-and-up">
|
|
<div class="input-field col s12">
|
|
<select id="entities_side" onchange="insert(this.value)"> </select>
|
|
<label>Entities</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="hide-on-med-and-up">
|
|
<div class="input-field col s12">
|
|
<select onchange="insert(this.value)">
|
|
<option value="" disabled selected>Choose your option</option>
|
|
<option value="numeric_state">Numeric state</option>
|
|
<option value="state">State</option>
|
|
<option value="sun">Sun</option>
|
|
<option value="template">Template</option>
|
|
<option value="time">Time</option>
|
|
<option value="zone">Zone</option>
|
|
</select>
|
|
<label>Conditions</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="hide-on-med-and-up">
|
|
<div class="input-field col s12">
|
|
<select id="services_side" onchange="insert(this.value)"> </select>
|
|
<label>Services</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</br>
|
|
</br>
|
|
</br>
|
|
</ul>
|
|
</div>
|
|
<div class="row">
|
|
<ul id="ace_settings" class="side-nav">
|
|
<li><a class="center grey lighten-3 z-depth-1 subheader">Editor Settings</a></li>
|
|
<form class="row col s12" action="#">
|
|
<p class="col s12">
|
|
<a class="waves-effect waves-light btn light-blue" target="_blank" href="https://github.com/ajaxorg/ace/wiki/Default-Keyboard-Shortcuts">Keyboard Shortcuts</a>
|
|
</p>
|
|
<p class="col s12">
|
|
<input type="checkbox" class="blue_check" onclick="editor.setOption('animatedScroll', !editor.getOptions().animatedScroll)" id="animatedScroll" />
|
|
<Label for="animatedScroll">Animated Scroll</label>
|
|
</p>
|
|
<p class="col s12">
|
|
<input type="checkbox" class="blue_check" onclick="editor.setOption('behavioursEnabled', !editor.getOptions().behavioursEnabled)" id="behavioursEnabled" />
|
|
<Label for="behavioursEnabled">Behaviour Enabled</label>
|
|
</p>
|
|
<p class="col s12">
|
|
<input type="checkbox" class="blue_check" onclick="editor.setOption('displayIndentGuides', !editor.getOptions().displayIndentGuides)" id="displayIndentGuides" />
|
|
<Label for="displayIndentGuides">Display Indent Guides</label>
|
|
</p>
|
|
<p class="col s12">
|
|
<input type="checkbox" class="blue_check" onclick="editor.setOption('fadeFoldWidgets', !editor.getOptions().fadeFoldWidgets)" id="fadeFoldWidgets" />
|
|
<Label for="fadeFoldWidgets">Fade Fold Widgets</label>
|
|
</p>
|
|
<div class="input-field col s12">
|
|
<input type="number" onchange="editor.setOption('fontSize', parseInt(this.value))" min="6" id="fontSize">
|
|
<label class="active" for="fontSize">Font Size</label>
|
|
</div>
|
|
<p class="col s12">
|
|
<input type="checkbox" class="blue_check" onclick="editor.setOption('highlightActiveLine', !editor.getOptions().highlightActiveLine)" id="highlightActiveLine" />
|
|
<Label for="highlightActiveLine">Hightlight Active Line</label>
|
|
</p>
|
|
<p class="col s12">
|
|
<input type="checkbox" class="blue_check" onclick="editor.setOption('highlightGutterLine', !editor.getOptions().highlightGutterLine)" id="highlightGutterLine" />
|
|
<Label for="highlightGutterLine">Hightlight Gutter Line</label>
|
|
</p>
|
|
<p class="col s12">
|
|
<input type="checkbox" class="blue_check" onclick="editor.setOption('highlightSelectedWord', !editor.getOptions().highlightSelectedWord)" id="highlightSelectedWord" />
|
|
<Label for="highlightSelectedWord">Hightlight Selected Word</label>
|
|
</p>
|
|
<p class="col s12">
|
|
<input type="checkbox" class="blue_check" onclick="editor.setOption('hScrollBarAlwaysVisible', !editor.getOptions().hScrollBarAlwaysVisible)" id="hScrollBarAlwaysVisible" />
|
|
<Label for="hScrollBarAlwaysVisible">H Scroll Bar Always Visible</label>
|
|
</p>
|
|
<div class="input-field col s12">
|
|
<select onchange="editor.setKeyboardHandler(this.value)" id="setKeyboardHandler">
|
|
<option value="">ace</option>
|
|
<option value="ace/keyboard/vim">vim</option>
|
|
<option value="ace/keyboard/emacs">emacs</option>
|
|
</select>
|
|
<label for="setKeyboardHandler">Keyboard Handler</label>
|
|
</div>
|
|
<div class="input-field col s12">
|
|
<select onchange="editor.setOption('mode', this.value)" id="mode">
|
|
<option value="ace/mode/abap">abap</option>
|
|
<option value="ace/mode/abc">abc</option>
|
|
<option value="ace/mode/actionscript">actionscript</option>
|
|
<option value="ace/mode/ada">ada</option>
|
|
<option value="ace/mode/apache_conf">apache_conf</option>
|
|
<option value="ace/mode/asciidoc">asciidoc</option>
|
|
<option value="ace/mode/assembly_x86">assembly_x86</option>
|
|
<option value="ace/mode/autohotkey">autohotkey</option>
|
|
<option value="ace/mode/batchfile">batchfile</option>
|
|
<option value="ace/mode/bro">bro</option>
|
|
<option value="ace/mode/c_cpp">c_cpp</option>
|
|
<option value="ace/mode/c9search">c9search</option>
|
|
<option value="ace/mode/cirru">cirru</option>
|
|
<option value="ace/mode/clojure">clojure</option>
|
|
<option value="ace/mode/cobol">cobol</option>
|
|
<option value="ace/mode/coffee">coffee</option>
|
|
<option value="ace/mode/coldfusion">coldfusion</option>
|
|
<option value="ace/mode/csharp">csharp</option>
|
|
<option value="ace/mode/css">css</option>
|
|
<option value="ace/mode/curly">curly</option>
|
|
<option value="ace/mode/d">d</option>
|
|
<option value="ace/mode/dart">dart</option>
|
|
<option value="ace/mode/diff">diff</option>
|
|
<option value="ace/mode/django">django</option>
|
|
<option value="ace/mode/dockerfile">dockerfile</option>
|
|
<option value="ace/mode/dot">dot</option>
|
|
<option value="ace/mode/drools">drools</option>
|
|
<option value="ace/mode/dummy">dummy</option>
|
|
<option value="ace/mode/dummysyntax">dummysyntax</option>
|
|
<option value="ace/mode/eiffel">eiffel</option>
|
|
<option value="ace/mode/ejs">ejs</option>
|
|
<option value="ace/mode/elixir">elixir</option>
|
|
<option value="ace/mode/elm">elm</option>
|
|
<option value="ace/mode/erlang">erlang</option>
|
|
<option value="ace/mode/forth">forth</option>
|
|
<option value="ace/mode/fortran">fortran</option>
|
|
<option value="ace/mode/ftl">ftl</option>
|
|
<option value="ace/mode/gcode">gcode</option>
|
|
<option value="ace/mode/gherkin">gherkin</option>
|
|
<option value="ace/mode/gitignore">gitignore</option>
|
|
<option value="ace/mode/glsl">glsl</option>
|
|
<option value="ace/mode/gobstones">gobstones</option>
|
|
<option value="ace/mode/golang">golang</option>
|
|
<option value="ace/mode/groovy">groovy</option>
|
|
<option value="ace/mode/haml">haml</option>
|
|
<option value="ace/mode/handlebars">handlebars</option>
|
|
<option value="ace/mode/haskell">haskell</option>
|
|
<option value="ace/mode/haskell_cabal">haskell_cabal</option>
|
|
<option value="ace/mode/haxe">haxe</option>
|
|
<option value="ace/mode/hjson">hjson</option>
|
|
<option value="ace/mode/html">html</option>
|
|
<option value="ace/mode/html_elixir">html_elixir</option>
|
|
<option value="ace/mode/html_ruby">html_ruby</option>
|
|
<option value="ace/mode/ini">ini</option>
|
|
<option value="ace/mode/io">io</option>
|
|
<option value="ace/mode/jack">jack</option>
|
|
<option value="ace/mode/jade">jade</option>
|
|
<option value="ace/mode/java">java</option>
|
|
<option value="ace/mode/javascript">javascript</option>
|
|
<option value="ace/mode/json">json</option>
|
|
<option value="ace/mode/jsoniq">jsoniq</option>
|
|
<option value="ace/mode/jsp">jsp</option>
|
|
<option value="ace/mode/jsx">jsx</option>
|
|
<option value="ace/mode/julia">julia</option>
|
|
<option value="ace/mode/kotlin">kotlin</option>
|
|
<option value="ace/mode/latex">latex</option>
|
|
<option value="ace/mode/less">less</option>
|
|
<option value="ace/mode/liquid">liquid</option>
|
|
<option value="ace/mode/lisp">lisp</option>
|
|
<option value="ace/mode/livescript">livescript</option>
|
|
<option value="ace/mode/logiql">logiql</option>
|
|
<option value="ace/mode/lsl">lsl</option>
|
|
<option value="ace/mode/lua">lua</option>
|
|
<option value="ace/mode/luapage">luapage</option>
|
|
<option value="ace/mode/lucene">lucene</option>
|
|
<option value="ace/mode/makefile">makefile</option>
|
|
<option value="ace/mode/markdown">markdown</option>
|
|
<option value="ace/mode/mask">mask</option>
|
|
<option value="ace/mode/matlab">matlab</option>
|
|
<option value="ace/mode/maze">maze</option>
|
|
<option value="ace/mode/mel">mel</option>
|
|
<option value="ace/mode/mushcode">mushcode</option>
|
|
<option value="ace/mode/mysql">mysql</option>
|
|
<option value="ace/mode/nix">nix</option>
|
|
<option value="ace/mode/nsis">nsis</option>
|
|
<option value="ace/mode/objectivec">objectivec</option>
|
|
<option value="ace/mode/ocaml">ocaml</option>
|
|
<option value="ace/mode/pascal">pascal</option>
|
|
<option value="ace/mode/perl">perl</option>
|
|
<option value="ace/mode/pgsql">pgsql</option>
|
|
<option value="ace/mode/php">php</option>
|
|
<option value="ace/mode/powershell">powershell</option>
|
|
<option value="ace/mode/praat">praat</option>
|
|
<option value="ace/mode/prolog">prolog</option>
|
|
<option value="ace/mode/properties">properties</option>
|
|
<option value="ace/mode/protobuf">protobuf</option>
|
|
<option value="ace/mode/python">python</option>
|
|
<option value="ace/mode/r">r</option>
|
|
<option value="ace/mode/razor">razor</option>
|
|
<option value="ace/mode/rdoc">rdoc</option>
|
|
<option value="ace/mode/rhtml">rhtml</option>
|
|
<option value="ace/mode/rst">rst</option>
|
|
<option value="ace/mode/ruby">ruby</option>
|
|
<option value="ace/mode/rust">rust</option>
|
|
<option value="ace/mode/sass">sass</option>
|
|
<option value="ace/mode/scad">scad</option>
|
|
<option value="ace/mode/scala">scala</option>
|
|
<option value="ace/mode/scheme">scheme</option>
|
|
<option value="ace/mode/scss">scss</option>
|
|
<option value="ace/mode/sh">sh</option>
|
|
<option value="ace/mode/sjs">sjs</option>
|
|
<option value="ace/mode/smarty">smarty</option>
|
|
<option value="ace/mode/snippets">snippets</option>
|
|
<option value="ace/mode/soy_template">soy_template</option>
|
|
<option value="ace/mode/space">space</option>
|
|
<option value="ace/mode/sql">sql</option>
|
|
<option value="ace/mode/sqlserver">sqlserver</option>
|
|
<option value="ace/mode/stylus">stylus</option>
|
|
<option value="ace/mode/svg">svg</option>
|
|
<option value="ace/mode/swift">swift</option>
|
|
<option value="ace/mode/tcl">tcl</option>
|
|
<option value="ace/mode/tex">tex</option>
|
|
<option value="ace/mode/text">text</option>
|
|
<option value="ace/mode/textile">textile</option>
|
|
<option value="ace/mode/toml">toml</option>
|
|
<option value="ace/mode/tsx">tsx</option>
|
|
<option value="ace/mode/twig">twig</option>
|
|
<option value="ace/mode/typescript">typescript</option>
|
|
<option value="ace/mode/vala">vala</option>
|
|
<option value="ace/mode/vbscript">vbscript</option>
|
|
<option value="ace/mode/velocity">velocity</option>
|
|
<option value="ace/mode/verilog">verilog</option>
|
|
<option value="ace/mode/vhdl">vhdl</option>
|
|
<option value="ace/mode/wollok">wollok</option>
|
|
<option value="ace/mode/xml">xml</option>
|
|
<option value="ace/mode/xquery">xquery</option>
|
|
<option value="ace/mode/yaml">yaml</option>
|
|
</select>
|
|
<label for="mode">Mode</label>
|
|
</div>
|
|
<div class="input-field col s12">
|
|
<select onchange="editor.setOption('newLineMode', this.value)" id="newLineMode">
|
|
<option value="auto">Auto</option>
|
|
<option value="windows">Windows</option>
|
|
<option value="unix">Unix</option>
|
|
</select>
|
|
<label for="newLineMode">New Line Mode</label>
|
|
</div>
|
|
<p class="col s12">
|
|
<input type="checkbox" class="blue_check" onclick="editor.setOption('overwrite', !editor.getOptions().overwrite)" id="overwrite" />
|
|
<Label for="overwrite">Overwrite</label>
|
|
</p>
|
|
<p class="col s12">
|
|
<input type="checkbox" class="blue_check" onclick="editor.setOption('readOnly', !editor.getOptions().readOnly)" id="readOnly" />
|
|
<Label for="readOnly">Read Only</label>
|
|
</p>
|
|
<div class="input-field col s12">
|
|
<input value="2" type="number" onchange="editor.setOption('scrollSpeed', parseInt(this.value))" id="scrollSpeed">
|
|
<label class="active" for="scrollSpeed">Scroll Speed</label>
|
|
</div>
|
|
<p class="col s12">
|
|
<input type="checkbox" class="blue_check" onclick="editor.setOption('showFoldWidgets', !editor.getOptions().showFoldWidgets)" id="showFoldWidgets" />
|
|
<Label for="showFoldWidgets">Show Fold Widgets</label>
|
|
</p>
|
|
<p class="col s12">
|
|
<input type="checkbox" class="blue_check" onclick="editor.setOption('showGutter', !editor.getOptions().showGutter)" id="showGutter" />
|
|
<Label for="showGutter">Show Gutter</label>
|
|
</p>
|
|
<p class="col s12">
|
|
<input type="checkbox" class="blue_check" onclick="editor.setOption('showInvisibles', !editor.getOptions().showInvisibles)" id="showInvisibles" />
|
|
<Label for="showInvisibles">Show Invisibles</label>
|
|
</p>
|
|
<p class="col s12">
|
|
<input type="checkbox" class="blue_check" onclick="editor.setOption('showPrintMargin', !editor.getOptions().showPrintMargin)" id="showPrintMargin" />
|
|
<Label for="showPrintMargin">Show Print Margin</label>
|
|
</p>
|
|
<p class="col s12">
|
|
<input type="checkbox" class="blue_check" onclick="editor.setOption('showLineNumbers', !editor.getOptions().showLineNumbers)" id="showLineNumbers" />
|
|
<Label for="showLineNumbers">Show Line Numbers</label>
|
|
</p>
|
|
<div class="input-field col s12">
|
|
<input type="number" onchange="editor.setOption('tabSize', parseInt(this.value))" min="1" id="tabSize">
|
|
<label class="active" for="tabSize">Tab Size</label>
|
|
</div>
|
|
<div class="input-field col s12">
|
|
<select onchange="editor.setTheme(this.value)" id="theme">
|
|
<optgroup label="Light Themes">
|
|
<option value="ace/theme/chrome">Chrome</option>
|
|
<option value="ace/theme/clouds">Clouds</option>
|
|
<option value="ace/theme/crimson_editor">Crimson Editor</option>
|
|
<option value="ace/theme/dawn">Dawn</option>
|
|
<option value="ace/theme/dreamweaver">Dreamweaver</option>
|
|
<option value="ace/theme/eclipse">Eclipse</option>
|
|
<option value="ace/theme/github">GitHub</option>
|
|
<option value="ace/theme/iplastic">IPlastic</option>
|
|
<option value="ace/theme/solarized_light">Solarized Light</option>
|
|
<option value="ace/theme/textmate">TextMate</option>
|
|
<option value="ace/theme/tomorrow">Tomorrow</option>
|
|
<option value="ace/theme/xcode">XCode</option>
|
|
<option value="ace/theme/kuroir">Kuroir</option>
|
|
<option value="ace/theme/katzenmilch">KatzenMilch</option>
|
|
<option value="ace/theme/sqlserver">SQL Server</option>
|
|
</optgroup>
|
|
<optgroup label="Dark Themes">
|
|
<option value="ace/theme/ambiance">Ambiance</option>
|
|
<option value="ace/theme/chaos">Chaos</option>
|
|
<option value="ace/theme/clouds_midnight">Clouds Midnight</option>
|
|
<option value="ace/theme/cobalt">Cobalt</option>
|
|
<option value="ace/theme/gruvbox">Gruvbox</option>
|
|
<option value="ace/theme/idle_fingers">idle Fingers</option>
|
|
<option value="ace/theme/kr_theme">krTheme</option>
|
|
<option value="ace/theme/merbivore">Merbivore</option>
|
|
<option value="ace/theme/merbivore_soft">Merbivore Soft</option>
|
|
<option value="ace/theme/mono_industrial">Mono Industrial</option>
|
|
<option value="ace/theme/monokai">Monokai</option>
|
|
<option value="ace/theme/pastel_on_dark">Pastel on dark</option>
|
|
<option value="ace/theme/solarized_dark">Solarized Dark</option>
|
|
<option value="ace/theme/terminal">Terminal</option>
|
|
<option value="ace/theme/tomorrow_night">Tomorrow Night</option>
|
|
<option value="ace/theme/tomorrow_night_blue">Tomorrow Night Blue</option>
|
|
<option value="ace/theme/tomorrow_night_bright">Tomorrow Night Bright</option>
|
|
<option value="ace/theme/tomorrow_night_eighties">Tomorrow Night 80s</option>
|
|
<option value="ace/theme/twilight">Twilight</option>
|
|
<option value="ace/theme/vibrant_ink">Vibrant Ink</option>
|
|
</optgroup>
|
|
</select>
|
|
<label for="theme">Theme</label>
|
|
</div>
|
|
<p class="col s12">
|
|
<input type="checkbox" class="blue_check" onclick="editor.setOption('useSoftTabs', !editor.getOptions().useSoftTabs)" id="useSoftTabs" />
|
|
<Label for="useSoftTabs">Use Soft Tabs</label>
|
|
</p>
|
|
<p class="col s12">
|
|
<input type="checkbox" class="blue_check" onclick="editor.setOption('useWorker', !editor.getOptions().useWorker)" id="useWorker" />
|
|
<Label for="useWorker">Use Worker</label>
|
|
</p>
|
|
<p class="col s12">
|
|
<input type="checkbox" class="blue_check" onclick="editor.setOption('vScrollBarAlwaysVisible', !editor.getOptions().vScrollBarAlwaysVisible)" id="vScrollBarAlwaysVisible" />
|
|
<Label for="vScrollBarAlwaysVisible">V Scroll Bar Always Visible</label>
|
|
</p>
|
|
<p class="col s12">
|
|
<input type="checkbox" class="blue_check" onclick="editor.setOption('wrapBehavioursEnabled', !editor.getOptions().wrapBehavioursEnabled)" id="wrapBehavioursEnabled" />
|
|
<Label for="wrapBehavioursEnabled">Wrap Behaviours Enabled</label>
|
|
</p>
|
|
<p class="col s12">
|
|
<input type="checkbox" class="blue_check" onclick="editor.getSession().setUseWrapMode(!editor.getSession().getUseWrapMode());if(editor.getSession().getUseWrapMode()){document.getElementById('wrap_limit').focus();document.getElementById('wrap_limit').onchange();}" id="wrap" />
|
|
<Label for="wrap">Wrap Mode</label>
|
|
</p>
|
|
<div class="input-field col s12">
|
|
<input id="wrap_limit" type="number" onchange="editor.setOption('wrap', parseInt(this.value))" min="1" value="80">
|
|
<label class="active" for="wrap_limit">Wrap Limit</label>
|
|
</div>
|
|
<a class="waves-effect waves-light btn light-blue" onclick="save_ace_settings()">Save Settings Locally</a>
|
|
<p class="center col s12"> Ace Editor 1.2.6 </p>
|
|
</form>
|
|
</ul>
|
|
</div>
|
|
<input type="hidden" id="currentfile" value="" />
|
|
</body>
|
|
<!-- Scripts-->
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.98.0/js/materialize.min.js"></script>
|
|
<script type="text/javascript">
|
|
$(document).ready(function () {
|
|
$('select').material_select();
|
|
$('.modal').modal();
|
|
$('.collapsible').collapsible();
|
|
$('.dropdown-button').dropdown({
|
|
inDuration: 300,
|
|
outDuration: 225,
|
|
constrainWidth: true,
|
|
hover: false,
|
|
gutter: 0,
|
|
belowOrigin: true,
|
|
alignment: 'right',
|
|
stopPropagation: false
|
|
});
|
|
$('.button-collapse').sideNav({
|
|
menuWidth: 350,
|
|
edge: 'left',
|
|
closeOnClick: false,
|
|
draggable: true
|
|
});
|
|
$('.ace_settings-collapse').sideNav({
|
|
menuWidth: 320, // Default is 300
|
|
edge: 'right', // Choose the horizontal origin
|
|
closeOnClick: true, // Closes side-nav on <a> clicks, useful for Angular/Meteor
|
|
draggable: true // Choose whether you can drag to open on touch screens
|
|
});
|
|
listdir('.');
|
|
});
|
|
</script>
|
|
<script>
|
|
var bootstrap = $bootstrap;
|
|
if (bootstrap.hasOwnProperty("events")) {
|
|
var events = document.getElementById("events");
|
|
for (var i = 0; i < bootstrap.events.length; i++) {
|
|
var option = document.createElement("option");
|
|
option.value = bootstrap.events[i].event;
|
|
option.text = bootstrap.events[i].event;
|
|
events.add(option);
|
|
}
|
|
var events = document.getElementById("events_side");
|
|
for (var i = 0; i < bootstrap.events.length; i++) {
|
|
var option = document.createElement("option");
|
|
option.value = bootstrap.events[i].event;
|
|
option.text = bootstrap.events[i].event;
|
|
events.add(option);
|
|
}
|
|
var entities = document.getElementById("entities");
|
|
for (var i = 0; i < bootstrap.states.length; i++) {
|
|
var option = document.createElement("option");
|
|
option.value = bootstrap.states[i].entity_id;
|
|
option.text = bootstrap.states[i].attributes.friendly_name + ' (' + bootstrap.states[i].entity_id + ')';
|
|
entities.add(option);
|
|
}
|
|
var entities = document.getElementById("entities_side");
|
|
for (var i = 0; i < bootstrap.states.length; i++) {
|
|
var option = document.createElement("option");
|
|
option.value = bootstrap.states[i].entity_id;
|
|
option.text = bootstrap.states[i].attributes.friendly_name + ' (' + bootstrap.states[i].entity_id + ')';
|
|
entities.add(option);
|
|
}
|
|
var services = document.getElementById("services");
|
|
for (var i = 0; i < bootstrap.services.length; i++) {
|
|
for (var k in bootstrap.services[i].services) {
|
|
var option = document.createElement("option");
|
|
option.value = bootstrap.services[i].domain + '.' + k;
|
|
option.text = bootstrap.services[i].domain + '.' + k;
|
|
services.add(option);
|
|
}
|
|
}
|
|
var services = document.getElementById("services_side");
|
|
for (var i = 0; i < bootstrap.services.length; i++) {
|
|
for (var k in bootstrap.services[i].services) {
|
|
var option = document.createElement("option");
|
|
option.value = bootstrap.services[i].domain + '.' + k;
|
|
option.text = bootstrap.services[i].domain + '.' + k;
|
|
services.add(option);
|
|
}
|
|
}
|
|
var options = $('#events option');
|
|
var arr = options.map(function (_, o) {
|
|
return {
|
|
t: $(o).text()
|
|
, v: o.value
|
|
};
|
|
}).get();
|
|
arr.sort(function (o1, o2) {
|
|
var t1 = o1.t.toLowerCase()
|
|
, t2 = o2.t.toLowerCase();
|
|
return t1 > t2 ? 1 : t1 < t2 ? -1 : 0;
|
|
});
|
|
options.each(function (i, o) {
|
|
o.value = arr[i].v;
|
|
$(o).text(arr[i].t);
|
|
});
|
|
var options = $('#entities option');
|
|
var arr = options.map(function (_, o) {
|
|
return {
|
|
t: $(o).text()
|
|
, v: o.value
|
|
};
|
|
}).get();
|
|
arr.sort(function (o1, o2) {
|
|
var t1 = o1.t.toLowerCase()
|
|
, t2 = o2.t.toLowerCase();
|
|
return t1 > t2 ? 1 : t1 < t2 ? -1 : 0;
|
|
});
|
|
options.each(function (i, o) {
|
|
o.value = arr[i].v;
|
|
$(o).text(arr[i].t);
|
|
});
|
|
var options = $('#services option');
|
|
var arr = options.map(function (_, o) {
|
|
return {
|
|
t: $(o).text()
|
|
, v: o.value
|
|
};
|
|
}).get();
|
|
arr.sort(function (o1, o2) {
|
|
var t1 = o1.t.toLowerCase()
|
|
, t2 = o2.t.toLowerCase();
|
|
return t1 > t2 ? 1 : t1 < t2 ? -1 : 0;
|
|
});
|
|
options.each(function (i, o) {
|
|
o.value = arr[i].v;
|
|
$(o).text(arr[i].t);
|
|
});
|
|
}
|
|
function listdir(path) {
|
|
$.get(encodeURI("api/listdir?path=" + path), function(data) {
|
|
renderpath(data);
|
|
});
|
|
}
|
|
|
|
function renderitem(itemdata) {
|
|
var item = document.createElement('a');
|
|
item.classList.add('collection-item');
|
|
item.href = '#';
|
|
var iicon = document.createElement('i');
|
|
iicon.classList.add('material-icons');
|
|
if (itemdata.type == 'dir') {
|
|
iicon.innerHTML = 'folder';
|
|
item.setAttribute("onclick", "listdir('" + encodeURI(itemdata.fullpath) + "')");
|
|
}
|
|
else {
|
|
iicon.innerHTML = 'insert_drive_file';
|
|
item.setAttribute("onclick", "loadfile('" + encodeURI(itemdata.fullpath) + "')");
|
|
}
|
|
item.appendChild(iicon);
|
|
var itext = document.createElement('span');
|
|
itext.innerHTML = itemdata.name;
|
|
itext.classList.add('filename');
|
|
item.appendChild(itext);
|
|
return item;
|
|
}
|
|
|
|
function renderpath(dirdata) {
|
|
var filebrowser = document.getElementById("filebrowser");
|
|
while (filebrowser.firstChild) {
|
|
filebrowser.removeChild(filebrowser.firstChild);
|
|
}
|
|
var collection = document.createElement('div');
|
|
collection.classList.add('collection');
|
|
collection.classList.add('with-header');
|
|
var fbheader = document.createElement('span');
|
|
fbheader.innerHTML = dirdata.abspath;
|
|
fbheader.classList.add('collection-header');
|
|
fbheader.id = 'fbheader';
|
|
collection.appendChild(fbheader);
|
|
var up = document.createElement('a');
|
|
up.classList.add('collection-item');
|
|
up.href = '#';
|
|
up.id = "uplink";
|
|
up.setAttribute("onclick", "listdir('" + encodeURI(dirdata.parent) + "')")
|
|
var upicon = document.createElement('i');
|
|
upicon.classList.add('material-icons');
|
|
upicon.innerHTML = 'folder';
|
|
up.appendChild(upicon);
|
|
var uptext = document.createElement('span');
|
|
uptext.innerHTML = '..';
|
|
uptext.classList.add('filename');
|
|
up.appendChild(uptext);
|
|
collection.appendChild(up);
|
|
|
|
for (var i = 0; i < dirdata.content.length; i++) {
|
|
collection.appendChild(renderitem(dirdata.content[i]));
|
|
}
|
|
|
|
filebrowser.appendChild(collection);
|
|
}
|
|
|
|
function loadfile(filepath) {
|
|
$.get("api/file?filename=" + filepath, function(data) {
|
|
editor.setValue(data);
|
|
editor.selection.selectFileStart();
|
|
editor.focus();
|
|
document.getElementById('currentfile').value = filepath;
|
|
});
|
|
}
|
|
|
|
function restart() {
|
|
$.get("api/restart", function (resp) {
|
|
if (resp.length == 0) {
|
|
var $toastContent = $("<div><pre>Restarting HASS</pre></div>");
|
|
Materialize.toast($toastContent, 2000);
|
|
}
|
|
else {
|
|
var $toastContent = $("<div><pre>" + resp + "</pre></div>");
|
|
Materialize.toast($toastContent, 2000);
|
|
}
|
|
});
|
|
}
|
|
|
|
function save() {
|
|
var filepath = document.getElementById('currentfile').value;
|
|
if (filepath.length > 0) {
|
|
data = new Object();
|
|
data.filename = filepath;
|
|
data.text = editor.getValue()
|
|
$.post("api/save", data).done(function(resp) {
|
|
var $toastContent = $("<div><pre>" + resp + "</pre></div>");
|
|
Materialize.toast($toastContent, 2000);
|
|
});
|
|
}
|
|
}
|
|
</script>
|
|
<script>
|
|
ace.require("ace/ext/language_tools");
|
|
var editor = ace.edit("editor");
|
|
if (localStorage.hasOwnProperty("pochass")) {
|
|
editor.setOptions(JSON.parse(localStorage.pochass));
|
|
editor.setOptions({
|
|
enableBasicAutocompletion: true,
|
|
enableSnippets: true
|
|
})
|
|
editor.$blockScrolling = Infinity;
|
|
}
|
|
else {
|
|
editor.getSession().setMode("ace/mode/yaml");
|
|
editor.setOptions({
|
|
showInvisibles: true,
|
|
useSoftTabs: true,
|
|
displayIndentGuides: true,
|
|
highlightSelectedWord: true,
|
|
enableBasicAutocompletion: true,
|
|
enableSnippets: true
|
|
})
|
|
editor.$blockScrolling = Infinity;
|
|
}
|
|
|
|
function apply_settings() {
|
|
var options = editor.getOptions();
|
|
for (var key in options) {
|
|
if (options.hasOwnProperty(key)) {
|
|
var target = document.getElementById(key);
|
|
if (target) {
|
|
if (typeof(options[key]) == "boolean" && target.type === 'checkbox') {
|
|
target.checked = options[key];
|
|
target.setAttribute("checked", options[key]);
|
|
}
|
|
else if (typeof(options[key]) == "number" && target.type === 'number') {
|
|
target.value = options[key];
|
|
}
|
|
else if (typeof(options[key]) == "string" && target.tagName == 'SELECT') {
|
|
console.log(key);
|
|
console.log(options[key]);
|
|
target.value = options[key];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
apply_settings();
|
|
|
|
function save_ace_settings() {
|
|
localStorage.pochass = JSON.stringify(editor.getOptions())
|
|
Materialize.toast("Ace Settings Saved", 2000);
|
|
}
|
|
|
|
function insert(text) {
|
|
var pos = editor.selection.getCursor();
|
|
var end = editor.session.insert(pos, text);
|
|
editor.selection.setRange({
|
|
start: pos,
|
|
end: end
|
|
});
|
|
editor.focus();
|
|
}
|
|
|
|
var foldstatus = true;
|
|
|
|
function toggle_fold() {
|
|
// Not used for now. We'll put a few buttons on top of the editor -> Toolbar. (Search, folding etc.)
|
|
if (foldstatus) {
|
|
editor.getSession().foldAll();
|
|
}
|
|
else {
|
|
editor.getSession().unfold();
|
|
}
|
|
foldstatus = !foldstatus;
|
|
}
|
|
|
|
function settings() {
|
|
editor.execCommand('showSettingsMenu');
|
|
}
|
|
</script>
|
|
</html>""")
|
|
|
|
def signal_handler(signal, frame):
|
|
global HTTPD
|
|
print("Shutting down server")
|
|
HTTPD.server_close()
|
|
sys.exit(0)
|
|
|
|
def load_settings(settingsfile):
|
|
global LISTENIP, LISTENPORT, BASEPATH, SSL_CERTIFICATE, SSL_KEY, HASS_API, \
|
|
HASS_API_PASSWORD, CREDENTIALS, ALLOWED_NETWORKS, BANNED_IPS, BANLIMIT
|
|
try:
|
|
if os.path.isfile(settingsfile):
|
|
with open(settingsfile) as fptr:
|
|
settings = json.loads(fptr.read())
|
|
LISTENIP = settings.get("LISTENIP", LISTENIP)
|
|
LISTENPORT = settings.get("LISTENPORT", LISTENPORT)
|
|
BASEPATH = settings.get("BASEPATH", BASEPATH)
|
|
SSL_CERTIFICATE = settings.get("SSL_CERTIFICATE", SSL_CERTIFICATE)
|
|
SSL_KEY = settings.get("SSL_KEY", SSL_KEY)
|
|
HASS_API = settings.get("HASS_API", HASS_API)
|
|
HASS_API_PASSWORD = settings.get("HASS_API_PASSWORD", HASS_API_PASSWORD)
|
|
CREDENTIALS = settings.get("CREDENTIALS", CREDENTIALS)
|
|
ALLOWED_NETWORKS = settings.get("ALLOWED_NETWORKS", ALLOWED_NETWORKS)
|
|
BANNED_IPS = settings.get("BANNED_IPS", BANNED_IPS)
|
|
BANLIMIT = settings.get("BANLIMIT", BANLIMIT)
|
|
except Exception as err:
|
|
print(err)
|
|
print("Not loading static settings")
|
|
return False
|
|
|
|
def get_dircontent(path):
|
|
dircontent = []
|
|
for e in sorted(os.listdir(path), key=lambda x: x.lower()):
|
|
edata = {}
|
|
edata['name'] = e
|
|
edata['dir'] = path
|
|
edata['fullpath'] = os.path.abspath(os.path.join(path, e))
|
|
edata['type'] = 'dir' if os.path.isdir(edata['fullpath']) else 'file'
|
|
dircontent.append(edata)
|
|
|
|
return dircontent
|
|
|
|
def get_html():
|
|
if DEV:
|
|
try:
|
|
with open("dev.html") as fptr:
|
|
html = Template(fptr.read())
|
|
return html
|
|
except Exception as err:
|
|
print(err)
|
|
print("Delivering embedded HTML")
|
|
return INDEX
|
|
|
|
def check_access(clientip):
|
|
global BANNED_IPS
|
|
if clientip in BANNED_IPS:
|
|
return False
|
|
if not ALLOWED_NETWORKS:
|
|
return True
|
|
for net in ALLOWED_NETWORKS:
|
|
ipobject = ipaddress.ip_address(clientip)
|
|
if ipobject in ipaddress.ip_network(net):
|
|
return True
|
|
BANNED_IPS.append(clientip)
|
|
return False
|
|
|
|
class RequestHandler(BaseHTTPRequestHandler):
|
|
def do_BLOCK(self):
|
|
self.send_response(420)
|
|
self.end_headers()
|
|
self.wfile.write(bytes("Policy not fulfilled", "utf8"))
|
|
|
|
def do_GET(self):
|
|
if not check_access(self.client_address[0]):
|
|
self.do_BLOCK()
|
|
return
|
|
req = urlparse(self.path)
|
|
query = parse_qs(req.query)
|
|
self.send_response(200)
|
|
if req.path == '/api/file':
|
|
content = ""
|
|
self.send_header('Content-type','text/text')
|
|
self.end_headers()
|
|
filename = query.get('filename', None)
|
|
try:
|
|
if filename:
|
|
filename = unquote(filename[0]).encode('utf-8')
|
|
print(filename)
|
|
if os.path.isfile(os.path.join(BASEDIR.encode('utf-8'), filename)):
|
|
with open(os.path.join(BASEDIR.encode('utf-8'), filename)) as fptr:
|
|
content += fptr.read()
|
|
else:
|
|
content = "File not found"
|
|
except Exception as err:
|
|
print(err)
|
|
content = str(err)
|
|
self.wfile.write(bytes(content, "utf8"))
|
|
return
|
|
elif req.path == '/api/listdir':
|
|
content = ""
|
|
self.send_header('Content-type','text/json')
|
|
self.end_headers()
|
|
dirpath = query.get('path', None)
|
|
try:
|
|
if dirpath:
|
|
dirpath = unquote(dirpath[0]).encode('utf-8')
|
|
if os.path.isdir(dirpath):
|
|
dircontent = get_dircontent(dirpath.decode('utf-8'))
|
|
filedata = {'content': dircontent,
|
|
'abspath': os.path.abspath(dirpath).decode('utf-8'),
|
|
'parent': os.path.dirname(os.path.abspath(dirpath)).decode('utf-8')
|
|
}
|
|
self.wfile.write(bytes(json.dumps(filedata), "utf8"))
|
|
except Exception as err:
|
|
print(err)
|
|
content = str(err)
|
|
self.wfile.write(bytes(content, "utf8"))
|
|
return
|
|
elif req.path == '/api/abspath':
|
|
content = ""
|
|
self.send_header('Content-type','text/text')
|
|
self.end_headers()
|
|
dirpath = query.get('path', None)
|
|
if dirpath:
|
|
dirpath = unquote(dirpath[0]).encode('utf-8')
|
|
print(dirpath)
|
|
absp = os.path.abspath(dirpath)
|
|
print(absp)
|
|
if os.path.isdir(dirpath):
|
|
self.wfile.write(os.path.abspath(dirpath))
|
|
return
|
|
elif req.path == '/api/parent':
|
|
content = ""
|
|
self.send_header('Content-type','text/text')
|
|
self.end_headers()
|
|
dirpath = query.get('path', None)
|
|
if dirpath:
|
|
dirpath = unquote(dirpath[0]).encode('utf-8')
|
|
print(dirpath)
|
|
absp = os.path.abspath(dirpath)
|
|
print(absp)
|
|
if os.path.isdir(dirpath):
|
|
self.wfile.write(os.path.abspath(os.path.dirname(dirpath)))
|
|
return
|
|
elif req.path == '/api/restart':
|
|
print("/api/restart")
|
|
self.send_header('Content-type','text/json')
|
|
self.end_headers()
|
|
r = {"restart": False}
|
|
try:
|
|
headers = {
|
|
"Content-Type": "application/json"
|
|
}
|
|
if HASS_API_PASSWORD:
|
|
headers["x-ha-access"] = HASS_API_PASSWORD
|
|
req = urllib.request.Request("%sservices/homeassistant/restart" % HASS_API, headers=headers, method='POST')
|
|
with urllib.request.urlopen(req) as response:
|
|
r = json.loads(response.read().decode('utf-8'))
|
|
print(r)
|
|
except Exception as err:
|
|
print(err)
|
|
r['restart'] = str(err)
|
|
self.wfile.write(bytes(json.dumps(r), "utf8"))
|
|
return
|
|
elif req.path == '/':
|
|
self.send_header('Content-type','text/html')
|
|
self.end_headers()
|
|
|
|
boot = "{}"
|
|
try:
|
|
headers = {
|
|
"Content-Type": "application/json"
|
|
}
|
|
if HASS_API_PASSWORD:
|
|
headers["x-ha-access"] = HASS_API_PASSWORD
|
|
req = urllib.request.Request("%sbootstrap" % HASS_API, headers=headers, method='GET')
|
|
with urllib.request.urlopen(req) as response:
|
|
boot = response.read().decode('utf-8')
|
|
|
|
except Exception as err:
|
|
print("Exception getting bootstrap")
|
|
print(err)
|
|
|
|
color = "green"
|
|
try:
|
|
response = urllib.request.urlopen(RELEASEURL)
|
|
latest = json.loads(response.read().decode('utf-8'))['tag_name']
|
|
if VERSION != latest:
|
|
color = "red"
|
|
except Exception as err:
|
|
print("Exception getting release")
|
|
print(err)
|
|
html = get_html().safe_substitute(bootstrap=boot, current=VERSION, versionclass=color)
|
|
self.wfile.write(bytes(html, "utf8"))
|
|
return
|
|
else:
|
|
self.send_response(404)
|
|
self.end_headers()
|
|
self.wfile.write(bytes("File not found", "utf8"))
|
|
|
|
def do_POST(self):
|
|
if not check_access(self.client_address[0]):
|
|
self.do_BLOCK()
|
|
return
|
|
postvars = {}
|
|
response = "Failure"
|
|
try:
|
|
length = int(self.headers['content-length'])
|
|
postvars = parse_qs(self.rfile.read(length).decode('utf-8'), keep_blank_values=1)
|
|
except Exception as err:
|
|
print(err)
|
|
response = "%s" % (str(err))
|
|
|
|
if 'filename' in postvars.keys() and 'text' in postvars.keys():
|
|
if postvars['filename'] and postvars['text']:
|
|
try:
|
|
filename = unquote(postvars['filename'][0])
|
|
with open(os.path.join(BASEDIR, filename), 'wb') as fptr:
|
|
fptr.write(bytes(postvars['text'][0], "utf-8"))
|
|
self.send_response(200)
|
|
self.end_headers()
|
|
self.wfile.write(bytes("File saved successfully", "utf8"))
|
|
return
|
|
except Exception as err:
|
|
response = "%s" % (str(err))
|
|
print(err)
|
|
else:
|
|
response = "Missing filename or text"
|
|
self.send_response(200)
|
|
self.end_headers()
|
|
self.wfile.write(bytes(response, "utf8"))
|
|
return
|
|
|
|
class AuthHandler(RequestHandler):
|
|
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'):
|
|
if BANLIMIT:
|
|
FAIL2BAN_IPS.pop(self.client_address[0], None)
|
|
super().do_GET()
|
|
pass
|
|
else:
|
|
if BANLIMIT:
|
|
bancounter = FAIL2BAN_IPS.get(self.client_address[0], 1)
|
|
if bancounter >= BANLIMIT:
|
|
print("Blocking access from %s" % self.client_address[0])
|
|
self.do_BLOCK()
|
|
return
|
|
else:
|
|
FAIL2BAN_IPS[self.client_address[0]] = bancounter + 1
|
|
self.do_AUTHHEAD()
|
|
self.wfile.write(bytes('Authentication required', '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'):
|
|
if BANLIMIT:
|
|
FAIL2BAN_IPS.pop(self.client_address[0], None)
|
|
super().do_POST()
|
|
pass
|
|
else:
|
|
if BANLIMIT:
|
|
bancounter = FAIL2BAN_IPS.get(self.client_address[0], 1)
|
|
if bancounter >= BANLIMIT:
|
|
print("Blocking access from %s" % self.client_address[0])
|
|
self.do_BLOCK()
|
|
return
|
|
else:
|
|
FAIL2BAN_IPS[self.client_address[0]] = bancounter + 1
|
|
self.do_AUTHHEAD()
|
|
self.wfile.write(bytes('Authentication required', 'utf-8'))
|
|
pass
|
|
|
|
def main(args):
|
|
global HTTPD, CREDENTIALS
|
|
if args:
|
|
load_settings(args[0])
|
|
print("Starting server")
|
|
server_address = (LISTENIP, LISTENPORT)
|
|
if CREDENTIALS:
|
|
CREDENTIALS = base64.b64encode(bytes(CREDENTIALS, "utf-8"))
|
|
Handler = AuthHandler
|
|
else:
|
|
Handler = RequestHandler
|
|
if not SSL_CERTIFICATE:
|
|
HTTPD = HTTPServer(server_address, Handler)
|
|
else:
|
|
HTTPD = socketserver.TCPServer(server_address, Handler)
|
|
HTTPD.socket = ssl.wrap_socket(HTTPD.socket, certfile=SSL_CERTIFICATE, keyfile=SSL_KEY, server_side=True)
|
|
print('Listening on: %s://%s:%i' % ('https' if SSL_CERTIFICATE else 'http', LISTENIP, LISTENPORT))
|
|
if BASEPATH:
|
|
os.chdir(BASEPATH)
|
|
HTTPD.serve_forever()
|
|
|
|
if __name__ == "__main__":
|
|
signal.signal(signal.SIGINT, signal_handler)
|
|
main(sys.argv[1:])
|