First pass at vite ui

This commit is contained in:
Johnny Gear 2026-01-09 21:44:55 -06:00
parent 4880ef61cf
commit 1f94855504
16 changed files with 276 additions and 42 deletions

View file

@ -12,6 +12,7 @@ peewee = "*"
peewee-migrate = "*"
flask = "*"
flask-login = "*"
flask-vite = "*"
[dev-packages]

11
Pipfile.lock generated
View file

@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "a598b3b97fc476eba41807a363b2528dfd7ec66f33b33f6b0703c004aaad22f9"
"sha256": "cbf2aa09aa805f5a444be073d6495925f6d37d04290e62140fb65bc02a170a75"
},
"pipfile-spec": 6,
"requires": {
@ -201,6 +201,15 @@
"markers": "python_version >= '3.7'",
"version": "==0.6.3"
},
"flask-vite": {
"hashes": [
"sha256:1681765668fa9dc7ca985f5ee395b1f7ed1d4db861407cccbcfdaf0346d4112c",
"sha256:5267abdd0bb4c8c9bb5b1a44b1260dc015a3f7e39befcf120babedc386d43558"
],
"index": "pypi",
"markers": "python_version >= '3.9' and python_version < '4.0'",
"version": "==0.6.1"
},
"frozenlist": {
"hashes": [
"sha256:0325024fe97f94c41c08872db482cf8ac4800d80e79222c6b0b7b162d5b13686",

View file

@ -30,6 +30,8 @@ class DomSubUsers(BaseModel):
class OrdersPool(BaseModel):
name = TextField()
user = ForeignKeyField(column_name='user_id', field='id', model=User, backref='orders_pools')
class Meta:
table_name = 'orders_pool'
@ -38,6 +40,8 @@ class Order(BaseModel):
weight = IntegerField()
repeat = FloatField()
pool = ForeignKeyField(column_name='orders_pool_id', field='id', model=OrdersPool, backref='orders')
class Meta:
table_name = 'order'

View file

@ -1,6 +1,6 @@
import datetime
from .models import database, User, DomSubUsers, Repeat, SkipDay, OrderStatus, PunishmentStatus
from .models import database, User, OrdersPool, DomSubUsers, Repeat, SkipDay, OrderStatus, PunishmentStatus
def initdb():
database.connect()
@ -22,6 +22,9 @@ def user_get(username):
telegram_username=username
)
def orders_pool_list(user_id):
return OrdersPool.select().where(OrdersPool.user_id == user_id)
def domsubusers_add(sub, dom):
return DomSubUsers.create(
sub=sub,
@ -34,6 +37,9 @@ def domsubusers_delete(sub, dom):
).delete()
return q.execute()
def domsubusers_list(dom):
return DomSubUsers.select().where(DomSubUsers.dom == dom)
def repeat_get():
try:
return Repeat.get()

42
flask/api.py Normal file
View file

@ -0,0 +1,42 @@
from flask import Blueprint, jsonify, abort
from flask_login import current_user
from db.queries import user_get, domsubusers_list, orders_pool_list
api = Blueprint('api', __name__)
@api.route('/subs')
def subs():
return jsonify(
[
{"sub_username": dsu.sub.telegram_username}
for dsu
in domsubusers_list(current_user.db_user)
]
)
@api.route('/subs/<username>/orders')
def sub_orders(username):
try:
sub = user_get(username)
except:
abort(500)
return
if sub.telegram_username not in [dsu.sub.telegram_username for dsu in domsubusers_list(current_user.db_user)]:
abort(500)
return
return jsonify([
{
'id': op.id,
'name': op.name,
'orders': [{
'id': order.id,
'name': order.name,
'weight': order.weight,
'repeat': order.repeat
} for order in op.orders]
}
for op
in orders_pool_list(sub.id)
])

View file

@ -6,10 +6,10 @@ from flask import Flask, render_template, request, redirect, flash
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
import hashlib
import hmac
import base64
from settings import FLASK_SECRET_KEY, TELEGRAM_API_TOKEN, TELEGRAM_BOT_NAME, TELEGRAM_BOT_DOMAIN
from db.queries import user_get
from api import api
app = Flask(__name__)
@ -34,17 +34,20 @@ def load_user(user_id):
except:
return None
app.register_blueprint(api, url_prefix='/api')
@app.route('/')
def index():
if not current_user.is_authenticated:
data = {'bot_name': TELEGRAM_BOT_NAME, 'bot_damin': TELEGRAM_BOT_DOMAIN}
return render_template('index.html', data = data)
else:
return redirect('/dashboard')
return redirect('/dashboard/')
@app.route('/dashboard')
@app.route('/dashboard/', defaults={'path': ''})
@app.route('/dashboard/<path:path>')
@login_required
def dashboard():
def dashboard(path):
return render_template('dashboard.html')
@app.route('/logout')
@ -90,7 +93,7 @@ def login():
return redirect('/')
return redirect('/dashboard')
return redirect('/dashboard/')
if __name__ == '__main__':
app.run(host='0.0.0.0', debug=True, port=8080)

View file

@ -1,35 +1,16 @@
<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Gear Orders Bot</title>
<style>
body {
font-family: Arial;
}
h1 {
font-size: 18pt;
}
.grid {
display: grid;
justify-items: center;
align-content: center;
height: 80vh;
}
</style>
<script type="module" src="/v/@vite/client"></script>
<script type="module" src="/v/src/main.tsx"></script>
</head>
<body>
<div class="grid">
<h1>Dashboard</h1>
<p>Successfuly logged in and the login verified</p>
</div>
</body>
<body>
<div id="root"></div>
</body>
</html>

View file

@ -1,7 +1,6 @@
<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

4
flask/vite/.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
/node_modules
/dist
/package-lock.json
/yarn.lock

11
flask/vite/index.html Normal file
View file

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Used only to please vite</title>
</head>
<body>
<script type="module" src="/main.js"></script>
</body>
</html>

24
flask/vite/package.json Normal file
View file

@ -0,0 +1,24 @@
{
"name": "demo-tailwind",
"version": "0.0.1",
"description": "",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"keywords": [],
"author": "",
"license": "MIT",
"devDependencies": {
"vite": "^5.0.11"
},
"dependencies": {
"@mantine/core": "^8.3.12",
"postcss": "^8.5.6",
"react": "^19.2.3",
"react-dom": "^19.2.3",
"react-router": "^7.12.0",
"typescript": "^5.9.3"
}
}

View file

@ -0,0 +1,76 @@
import {
Container,
Accordion,
TextInput,
Divider,
Input,
Slider,
} from "@mantine/core";
import React from "react";
import { Params, useLoaderData, useParams } from "react-router";
export const subOrdersLoader = async ({
params: { username },
}: {
params: Params<string>;
}) => fetch(`/api/subs/${username}/orders`).then((response) => response.json());
export const SubOrders: React.FC = () => {
const { username: sub_username } = useParams();
const orders = useLoaderData<
{
id: number;
name: string;
orders: {
id: number;
name: string;
weight: number;
repeat: number;
}[];
}[]
>();
return (
<Container>
<h1>Orders for {sub_username}</h1>
<Accordion>
{orders
? orders.map(({ id, name, orders }) => (
<Accordion.Item key={id} value={`${id}`}>
<Accordion.Control>{name}</Accordion.Control>
<Accordion.Panel>
<>
<TextInput label="Name" value={name} />
{orders.map(
({ id: order_id, name: order_name, weight, repeat }) => (
<React.Fragment key={order_id}>
<Divider my="md" />
<TextInput label="Name" value={order_name} />
<Input.Wrapper
label="Weight"
description="Chance this order will be selected"
>
<Slider domain={[0, 100]} value={weight} />
</Input.Wrapper>
<Input.Wrapper
label="Repeat"
description="Percent chance that this order will repeat the next day"
>
<Slider
domain={[0, 1]}
step={0.05}
value={repeat}
/>
</Input.Wrapper>
</React.Fragment>
)
)}
</>
</Accordion.Panel>
</Accordion.Item>
))
: null}
</Accordion>
</Container>
);
};

View file

@ -0,0 +1,21 @@
import React from "react";
import { Container } from "@mantine/core";
import { useLoaderData, Link } from "react-router";
export const subsListLoader = () =>
fetch("/api/subs").then((response) => response.json());
export const SubsList: React.FC = () => {
const subs = useLoaderData<{ sub_username: string }[]>();
return (
<Container>
<h1>Subs</h1>
{subs.map(({ sub_username }) => (
<Link key={sub_username} to={`/dashboard/subs/${sub_username}`}>
Orders for {sub_username}
</Link>
))}
</Container>
);
};

42
flask/vite/src/main.tsx Normal file
View file

@ -0,0 +1,42 @@
import React from "react";
import ReactDOM from "react-dom/client";
import { createBrowserRouter } from "react-router";
import { RouterProvider } from "react-router/dom";
import { createTheme, MantineProvider, Input } from "@mantine/core";
import "@mantine/core/styles.css";
import { SubsList, subsListLoader } from "./SubsList";
import { SubOrders, subOrdersLoader } from "./SubOrders";
const theme = createTheme({
components: {
InputWrapper: Input.Wrapper.extend({
defaultProps: {
inputWrapperOrder: ["label", "input", "description", "error"],
my: "md",
},
}),
},
});
const router = createBrowserRouter([
{
path: "dashboard",
Component: SubsList,
loader: subsListLoader,
},
{
path: "dashboard/subs/:username",
Component: SubOrders,
loader: subOrdersLoader,
},
]);
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<MantineProvider theme={theme}>
<RouterProvider router={router} />
</MantineProvider>
</React.StrictMode>
);

View file

@ -0,0 +1,9 @@
export default {
base: '/v/',
server: {
host: 'orders-dev.johnnygear.net',
port: 3000,
strictPort: true
}
};

View file

@ -63,24 +63,26 @@ def migrate(migrator: Migrator, database: pw.Database, *, fake=False):
table_name = "dom_sub"
indexes = [(('dom_id', 'sub_id'), True)]
@migrator.create_model
class OrdersPool(pw.Model):
id = pw.AutoField()
name = pw.TextField()
user = pw.ForeignKeyField(column_name='user_id', field='id', model=migrator.orm['user'])
class Meta:
table_name = "orders_pool"
@migrator.create_model
class Order(pw.Model):
id = pw.AutoField()
name = pw.TextField()
weight = pw.IntegerField()
repeat = pw.FloatField()
pool = pw.ForeignKeyField(column_name='orders_pool_id', field='id', model=migrator.orm['orders_pool'])
class Meta:
table_name = "order"
@migrator.create_model
class OrdersPool(pw.Model):
id = pw.AutoField()
name = pw.TextField()
class Meta:
table_name = "orders_pool"
@migrator.create_model
class PunishmentStatus(pw.Model):
id = pw.AutoField()
@ -135,10 +137,10 @@ def rollback(migrator: Migrator, database: pw.Database, *, fake=False):
migrator.remove_model('punishment_status')
migrator.remove_model('orders_pool')
migrator.remove_model('order')
migrator.remove_model('orders_pool')
migrator.remove_model('dom_sub')
migrator.remove_model('user')