Base of a new flask app

pull/1/head
Matt C 2 years ago
parent 00e7440192
commit 686d3262f8

5
.gitignore vendored

@ -1,3 +1,8 @@
# Custom
conf/
db/
packages/
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]

8
.idea/.gitignore vendored

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

@ -0,0 +1,14 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredPackages">
<value>
<list size="1">
<item index="0" class="java.lang.String" itemvalue="discord" />
</list>
</value>
</option>
</inspection_tool>
</profile>
</component>

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10 (webforge)" project-jdk-type="Python SDK" />
</project>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/webforge.iml" filepath="$PROJECT_DIR$/.idea/webforge.iml" />
</modules>
</component>
</project>

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
<mapping directory="$PROJECT_DIR$/conf" vcs="Git" />
<mapping directory="$PROJECT_DIR$/conf/any/base" vcs="Git" />
<mapping directory="$PROJECT_DIR$/conf/any/crystal-dev" vcs="Git" />
<mapping directory="$PROJECT_DIR$/conf/any/crystal-keyring" vcs="Git" />
<mapping directory="$PROJECT_DIR$/conf/any/filesystem" vcs="Git" />
<mapping directory="$PROJECT_DIR$/conf/any/gnome-shell-extension-arch-update" vcs="Git" />
<mapping directory="$PROJECT_DIR$/conf/any/gnome-shell-extension-caffeine" vcs="Git" />
<mapping directory="$PROJECT_DIR$/conf/any/gnome-shell-extension-dash-to-panel" vcs="Git" />
<mapping directory="$PROJECT_DIR$/conf/any/gnome-shell-extension-desktop-icons-neo" vcs="Git" />
<mapping directory="$PROJECT_DIR$/conf/any/gnome-shell-extension-gsconnect" vcs="Git" />
<mapping directory="$PROJECT_DIR$/conf/any/grub-theme" vcs="Git" />
<mapping directory="$PROJECT_DIR$/conf/any/lsb-release" vcs="Git" />
<mapping directory="$PROJECT_DIR$/conf/any/neofetch" vcs="Git" />
<mapping directory="$PROJECT_DIR$/conf/any/onyx" vcs="Git" />
<mapping directory="$PROJECT_DIR$/conf/any/pfetch" vcs="Git" />
<mapping directory="$PROJECT_DIR$/conf/any/pkg-warner" vcs="Git" />
<mapping directory="$PROJECT_DIR$/conf/any/timeshift" vcs="Git" />
<mapping directory="$PROJECT_DIR$/conf/any/timeshift-autosnap" vcs="Git" />
<mapping directory="$PROJECT_DIR$/conf/any/wallpapers" vcs="Git" />
<mapping directory="$PROJECT_DIR$/conf/x86_64/amethyst" vcs="Git" />
<mapping directory="$PROJECT_DIR$/conf/x86_64/grub" vcs="Git" />
<mapping directory="$PROJECT_DIR$/conf/x86_64/jade" vcs="Git" />
<mapping directory="$PROJECT_DIR$/conf/x86_64/jade-gui" vcs="Git" />
<mapping directory="$PROJECT_DIR$/conf/x86_64/malachite" vcs="Git" />
<mapping directory="$PROJECT_DIR$/conf/x86_64/zramd" vcs="Git" />
<mapping directory="$PROJECT_DIR$/packages" vcs="Git" />
</component>
</project>

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="Flask">
<option name="enabled" value="true" />
</component>
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/venv" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="PyDocumentationSettings">
<option name="format" value="PLAIN" />
<option name="myDocStringFormat" value="Plain" />
</component>
<component name="TemplatesService">
<option name="TEMPLATE_CONFIGURATION" value="Jinja2" />
<option name="TEMPLATE_FOLDERS">
<list>
<option value="$MODULE_DIR$/templates" />
</list>
</option>
</component>
</module>

@ -0,0 +1,6 @@
dst: conf
has_subdirs: true
src: git@github.com:crystal-linux/mlc-repos.git
subdirs:
- any
- x86_64

@ -1,56 +1,94 @@
import asyncio
import os,subprocess
import threading
# Stdlib
import os,yaml
from flask import Flask,render_template
# Pip
import flask_login
from flask import Flask,render_template,request,redirect,make_response
# In-house
from utils import run_command_shell
from users import usermgr
from mlcmanager import mlcmgr
app = Flask(__name__)
app.secret_key = "CorrectBatteryStapleHorseLol"
login_manager = flask_login.LoginManager()
login_manager.init_app(app)
db = usermgr()
mlc = mlcmgr()
class User(flask_login.UserMixin):
pass
@login_manager.user_loader
def user_loader(uid):
if not db.check_user_exists(uid):
return
user = User()
user.id = uid
return user
@login_manager.request_loader
def request_loader(request):
uid = request.form.get("uid")
if not db.check_user_exists(uid):
return
user = User()
user.id = uid
return user
@app.route("/login", methods=["GET", "POST"])
def login():
if request.method == "GET":
msg = request.cookies.get("msg")
hmsg = ""
if msg is not None:
hmsg = "<p style='color:red;'>" + msg + "</p>"
resp = make_response(
render_template(
"page.html",
page_title="Login",
content=hmsg + render_template("signin.html"),
)
)
if msg is not None:
resp.delete_cookie("msg")
return resp
else:
uid = request.form["uid"]
if db.check_user_exists(uid) and db.auth_user(uid, request.form["passwd"]):
user = User()
user.id = uid
flask_login.login_user(user, remember=True)
return redirect("/")
else:
resp = make_response(redirect("/login"))
resp.set_cookie("msg", "Login failed.")
return resp
@app.route("/logout")
def logout():
flask_login.logout_user()
return redirect("/")
@app.route("/")
def main():
return render_template("page.html", product="Web Forge", page="Home", content=render_template("console.html"))
@app.route("/run/<what>")
async def run(what):
res = await run_command_shell("/bin/bash -c '" + what + "'")
return res
# Maybe add: https://docs.python.org/3/library/shlex.html#shlex.quote ?
async def run_command_shell(command, grc=False):
"""Run command in subprocess (shell)."""
kill = lambda proc: proc.kill()
# Create subprocess
process = await asyncio.create_subprocess_shell(
command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
)
# Status
print("Started:", command, "(pid = " + str(process.pid) + ")", flush=True)
kill_timer = threading.Timer(60, kill, [process])
try:
# Wait for the subprocess to finish
kill_timer.start()
stdout, stderr = await process.communicate()
except:
kill_timer.cancel()
# Progress
if process.returncode == 0:
print("Done:", command, "(pid = " + str(process.pid) + ")", flush=True)
# Result
result = stdout.decode().strip()
if not flask_login.current_user.is_authenticated:
pc = "<a href='/login'>Login</a>"
else:
print("Failed:", command, "(pid = " + str(process.pid) + ")", flush=True)
# Result
result = stderr.decode().strip()
pc = f"<p>Hi, {flask_login.current_user.id}</p>" + render_template("logout.html")
return render_template("page.html", page_title="Home", content=pc)
kill_timer.cancel()
if not grc:
# Return stdout
return result
else:
return process.returncode, result
if __name__ == "__main__":
app.run(host="127.0.0.1", port=6969, debug=True)

@ -0,0 +1,98 @@
import os,yaml,contextlib
import subprocess
# https://stackoverflow.com/a/13847807
@contextlib.contextmanager
def pushd(new_dir):
previous_dir = os.getcwd()
os.chdir(new_dir)
try:
yield
finally:
os.chdir(previous_dir)
class mlcmgr:
def __init__(self):
if not os.path.exists("config.yml"):
src = input("Git url for configs repo: ")
dst = input("Where would you like the root to be (dir): ")
subdirs = []
has_subdirs = input("Does your repo have subdirs? (Y/n): ")
if has_subdirs != "n":
newd = 'foo'
while newd != "done":
newd = input("Subdir (or 'done'): ")
if newd == "done":
break
else:
subdirs.append(newd)
has_subdirs = True
else:
has_subdirs = False
settings = {
"src": src,
"dst": dst,
"has_subdirs": has_subdirs,
"subdirs": subdirs
}
with open("config.yml", "w") as f:
f.write(yaml.dump(settings))
self.config = yaml.safe_load(open("config.yml").read())
self.init_workspace()
def init_workspace(self):
if not os.path.exists(self.config["dst"]):
os.system(f"git clone {self.config['src']} {self.config['dst']}")
if not self.config['has_subdirs']:
os.system(f"cd {self.config['dst']} && mlc init")
else:
for subdir in self.config['subdirs']:
os.system(f"cd {self.config['dst']} && cd {subdir} && mlc init && cd ../")
def pull_all(self):
if self.config['has_subdirs']:
for subdir in self.config['subdirs']:
os.system(f"cd {self.config['dst']} && cd {subdir} && mlc pull && cd ../")
else:
os.system(f"cd {self.config['dst']} && mlc pull && cd ../")
return "Done"
def get_info(self, subdir=None):
extra = self.config['dst']
if subdir is not None:
extra += "/" + subdir
with pushd(extra):
out = subprocess.check_output(["mlc","info"]).decode('utf-8')
return out
def list_packages(self, subdir=None):
extra = self.config['dst']
if subdir is not None:
extra += "/" + subdir
return os.listdir(extra)
def build(self, package, subdir=None):
p = self.config['dst']
if subdir is not None:
p += "/" + subdir
with pushd(p):
os.system(f"mlc build {package}")
def gen_repo(self, subdir=None):
p = self.config['dst']
if subdir is not None:
p += "/" + subdir
with pushd(p):
os.system("mlc repo-gen")
if __name__ == "__main__":
mlc = mlcmgr()
#mlc.pull_all()
mlc.build("base", "any")

@ -1 +1,7 @@
Flask[async]
Flask[async]
flask-login
Flask-Limiter
PyYAML
passlib
black
gunicorn

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

@ -0,0 +1,68 @@
/*
Thanks Michal for the CSS
https://github.com/not-my-segfault/pronounce/blob/main/static/error.html
*/
* {
margin: 0;
padding: 0;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
::-webkit-scrollbar {
display: none;
}
.wrapper {
position: absolute;
top: 50%;
right: 50%;
-webkit-transform: translate(50%, -50%);
transform: translate(50%, -50%);
}
ul {
margin-top: 5px;
list-style: none;
white-space: nowrap;
}
li {
display: inline-block;
margin-right: 3px;
}
a {
-webkit-transition: ease-in-out 0.3s;
transition: ease-in-out 0.3s;
text-decoration: none;
}
u {
text-decoration: underline;
text-decoration-style: dashed;
}
body {
font-family: 'Inconsolata', monospace;
background: #0e0e0eff;
color: #eeeeeeff;
}
.wrapper h1 {
color: #555555ff;
}
.wrapper p {
color: #eeeeeecc;
}
input {
background-color: #414141;
color: #eeeeeecc;
border: none;
}
.nicebutton {
background-color: #414141;
color: #eeeeeecc;
border: none;
}
.slicklink {
color: #555555ff;
}

@ -1,27 +0,0 @@
<script>
document.body.addEventListener('keydown', function (e) {
if (e.keyCode === 13) {
// document.getElementById("output").innerHTML += "<br/>" + document.getElementById("thecommand").value;
var inputBox = document.getElementById("thecommand");
var whatDo = inputBox.value;
inputBox.value = "";
// Function to do an Ajax call
$.ajax({
url:"/run/"+whatDo,
type:"GET",
success: function(data) {
document.getElementById("output").innerHTML += "<br/>" + data;
},
});
}
});
</script>
<div class="input-group flex-nowrap">
<span class="input-group-text" id="addon-wrapping">$</span>
<input id="thecommand" type="text" class="form-control" placeholder="" aria-label="Shell" aria-describedby="addon-wrapping">
</div>
<pre>
<code id="output">
</code>
</pre>

@ -0,0 +1,2 @@
<button class="nicebutton" onclick="location.href='/logout'" type="button">Logout</button>
<br/>

@ -1,15 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{product}} - {{page}}</title>
<link href="/static/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
</head>
<body>
<h1>{{product}} - {{page}}</h1>
{{content|safe}}
<script src="/static/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
<script src="/static/jquery-3.6.0.min.js"></script>
</body>
</html>
<html>
<head>
<title>Web Forge: {{page_title}}</title>
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
<div class="wrapper">
<h1>Web Forge: {{page_title}}</h1>
{{content|safe}}
</div>
</body>
</html>

@ -0,0 +1,10 @@
<h4>Anything left blank will not be changed.</h4>
<form action="/settings" method="POST">
<h3>Timezone: </h3>
<input type='text' name='tz'/>
<h3>Time type: (12 or 24):</h3>
<input type="text" name="tztype"/>
<h3>Password: </h3>
<input type='password' name='passwd'/>
<button class='nicebutton' type='submit'>Update</button>
</form>

@ -0,0 +1,7 @@
<form action="login" method="POST">
<h3>Username: </h3>
<input type='text' name='uid'/>
<h3>Password: </h3>
<input type='password' name='passwd'/>
<button class='nicebutton' type='submit'>Login</button>
</form>

@ -0,0 +1,50 @@
import os,yaml
from passlib.hash import pbkdf2_sha256
class usermgr:
def __init__(self):
if not os.path.exists("db"):
os.makedirs("db")
self.srcdir = "db"
def write_user(self, uid, obj):
with open(self.srcdir + os.sep + uid, "w") as f:
f.write(yaml.dump(obj))
def get_user(self, uid):
if self.check_user_exists(uid):
with open(self.srcdir + os.sep + uid) as f:
obj = yaml.safe_load(f)
return obj
else:
return {"message": "not found"}
def check_user_exists(self, uid):
if uid == "" or uid is None:
return False
if os.path.exists(self.srcdir + os.sep + uid):
return True
return False
def auth_user(self, uid, attempt):
if self.check_user_exists(uid):
phash = self.get_user(uid)["passw"]
if pbkdf2_sha256.verify(attempt, phash):
return True
return False
def make_user(self, uid, passw):
obj = {"passw": pbkdf2_sha256.hash(passw)}
self.write_user(uid, obj)
def set_user_password(self, uid, newpass):
if self.check_user_exists(uid):
obj = self.get_user(uid)
hashed_pw = pbkdf2_sha256.hash(newpass)
obj["passw"] = hashed_pw
self.write_user(uid, obj)
return {"message": "done."}
else:
return {"message": f"error: no such user {uid}"}

@ -0,0 +1,41 @@
import asyncio,threading
# Maybe add: https://docs.python.org/3/library/shlex.html#shlex.quote ?
async def run_command_shell(command, grc=False):
"""Run command in subprocess (shell)."""
kill = lambda proc: proc.kill()
# Create subprocess
process = await asyncio.create_subprocess_shell(
command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
)
# Status
print("Started:", command, "(pid = " + str(process.pid) + ")", flush=True)
kill_timer = threading.Timer(60, kill, [process])
try:
# Wait for the subprocess to finish
kill_timer.start()
stdout, stderr = await process.communicate()
except:
kill_timer.cancel()
# Progress
if process.returncode == 0:
print("Done:", command, "(pid = " + str(process.pid) + ")", flush=True)
# Result
result = stdout.decode().strip()
else:
print("Failed:", command, "(pid = " + str(process.pid) + ")", flush=True)
# Result
result = stderr.decode().strip()
kill_timer.cancel()
if not grc:
# Return stdout
return result
else:
return process.returncode, result
Loading…
Cancel
Save