Base of a new flask app
parent
00e7440192
commit
686d3262f8
@ -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…
Reference in New Issue